ani-web 2.0.7
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.
- package/LICENSE +21 -0
- package/README.md +220 -0
- package/client/dist/assets/AnimeInfo-B88ZA3gl.js +1 -0
- package/client/dist/assets/AnimeInfo-R63luGTP.css +1 -0
- package/client/dist/assets/AnimeInfoPage-xGVarrXG.js +2 -0
- package/client/dist/assets/Button-Fq9KaUOg.css +1 -0
- package/client/dist/assets/Button-lkEUHIDS.js +1 -0
- package/client/dist/assets/ErrorMessage-BVWNgHMx.css +1 -0
- package/client/dist/assets/ErrorMessage-BXKDLzn8.js +1 -0
- package/client/dist/assets/Home-O1FbN8t_.js +1 -0
- package/client/dist/assets/Home-r0eYbWNc.css +1 -0
- package/client/dist/assets/Insights-CF4K-oO5.css +1 -0
- package/client/dist/assets/Insights-opcB-aTo.js +1 -0
- package/client/dist/assets/MAL-BGM33N5c.js +1 -0
- package/client/dist/assets/MAL-DeQNXXPx.css +1 -0
- package/client/dist/assets/Player-BarbgKjI.css +1 -0
- package/client/dist/assets/Player-CSyGax33.js +9 -0
- package/client/dist/assets/PlayerSettings-BaCVQyw6.js +1 -0
- package/client/dist/assets/PlayerSettings-l6aLKrHh.css +1 -0
- package/client/dist/assets/QueueRail-Cxn5U8kE.js +1 -0
- package/client/dist/assets/QueueRail-DOM_pWkE.css +1 -0
- package/client/dist/assets/RemoveConfirmationModal-BBiogSdf.css +1 -0
- package/client/dist/assets/RemoveConfirmationModal-DatCZQKq.js +1 -0
- package/client/dist/assets/Search-BzO-aRP7.css +1 -0
- package/client/dist/assets/Search-DJxo3BYH.js +1 -0
- package/client/dist/assets/SearchableSelect-BkCrf6E8.css +1 -0
- package/client/dist/assets/SearchableSelect-BzYsMz8B.js +1 -0
- package/client/dist/assets/Settings-Bv9fX-x3.css +1 -0
- package/client/dist/assets/Settings-C5adinOe.js +1 -0
- package/client/dist/assets/SynopsisText-C3AK-aRc.js +1 -0
- package/client/dist/assets/SynopsisText-DsI3mW5v.css +1 -0
- package/client/dist/assets/ToggleSwitch-BIlQxIjg.css +1 -0
- package/client/dist/assets/ToggleSwitch-CrXim14A.js +1 -0
- package/client/dist/assets/Watchlist-CXw0vbNx.js +1 -0
- package/client/dist/assets/Watchlist-a2RHQogs.css +1 -0
- package/client/dist/assets/hls.light-DcbkToIY.js +27 -0
- package/client/dist/assets/index-BzX_xmnf.css +1 -0
- package/client/dist/assets/index-Ciivz6fh.js +178 -0
- package/client/dist/assets/useAnimeInfoData-Dqthchpa.js +1 -0
- package/client/dist/assets/useIsMobile-BviODivc.js +1 -0
- package/client/dist/assets/vendor-Bc4EraM_.js +3 -0
- package/client/dist/favicon.ico +0 -0
- package/client/dist/index.html +35 -0
- package/client/dist/logo.png +0 -0
- package/client/dist/placeholder.svg +4 -0
- package/client/dist/robots.txt +3 -0
- package/client/package.json +58 -0
- package/orchestrator.js +323 -0
- package/package.json +88 -0
- package/server/.env +1 -0
- package/server/dist/config.js +89 -0
- package/server/dist/constants.json +1359 -0
- package/server/dist/controllers/auth.controller.js +215 -0
- package/server/dist/controllers/data.controller.js +232 -0
- package/server/dist/controllers/insights.controller.js +200 -0
- package/server/dist/controllers/proxy.controller.js +353 -0
- package/server/dist/controllers/settings.controller.js +159 -0
- package/server/dist/controllers/watchlist.controller.js +749 -0
- package/server/dist/db.js +152 -0
- package/server/dist/discord-rpc.js +279 -0
- package/server/dist/github-sync.js +342 -0
- package/server/dist/google.js +310 -0
- package/server/dist/logger.js +21 -0
- package/server/dist/providers/123anime.provider.js +226 -0
- package/server/dist/providers/allanime.provider.js +736 -0
- package/server/dist/providers/animepahe.provider.js +457 -0
- package/server/dist/providers/animeya.provider.js +787 -0
- package/server/dist/providers/megaplay.provider.js +264 -0
- package/server/dist/providers/provider.interface.js +2 -0
- package/server/dist/rclone.js +126 -0
- package/server/dist/repositories/insights.repository.js +42 -0
- package/server/dist/repositories/notifications.repository.js +30 -0
- package/server/dist/repositories/queue.repository.js +38 -0
- package/server/dist/repositories/settings.repository.js +14 -0
- package/server/dist/repositories/shows-meta.repository.js +41 -0
- package/server/dist/repositories/watched-episodes.repository.js +67 -0
- package/server/dist/repositories/watchlist.repository.js +80 -0
- package/server/dist/routes/auth.routes.js +26 -0
- package/server/dist/routes/data.routes.js +42 -0
- package/server/dist/routes/insights.routes.js +12 -0
- package/server/dist/routes/proxy.routes.js +14 -0
- package/server/dist/routes/settings.routes.js +27 -0
- package/server/dist/routes/watchlist.routes.js +46 -0
- package/server/dist/server.js +229 -0
- package/server/dist/sync-config.js +28 -0
- package/server/dist/sync.js +427 -0
- package/server/dist/utils/db-utils.js +15 -0
- package/server/dist/utils/env.utils.js +79 -0
- package/server/dist/utils/machine-id.js +46 -0
- package/server/dist/utils/request-context.js +5 -0
- package/server/package.json +19 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.AnimePaheProvider = void 0;
|
|
40
|
+
const cheerio = __importStar(require("cheerio"));
|
|
41
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
42
|
+
const request_context_1 = require("../utils/request-context");
|
|
43
|
+
class AnimePaheProvider {
|
|
44
|
+
name = 'AnimePahe';
|
|
45
|
+
BASE_URL = 'https://animepahe.pw';
|
|
46
|
+
API_URL = 'https://animepahe.pw/api';
|
|
47
|
+
cache;
|
|
48
|
+
constructor(cache) {
|
|
49
|
+
this.cache = cache;
|
|
50
|
+
}
|
|
51
|
+
async getRequestHeaders(isApi = false, customUaOverride, customCookieOverride) {
|
|
52
|
+
const store = request_context_1.requestContext.getStore();
|
|
53
|
+
const customUa = customUaOverride || store?.get('ua');
|
|
54
|
+
const customCookie = customCookieOverride || store?.get('cookie');
|
|
55
|
+
const userAgent = customUa ||
|
|
56
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36';
|
|
57
|
+
let cookieStr = '';
|
|
58
|
+
if (customCookie) {
|
|
59
|
+
// Sanitize cookie: remove 'cf_clearance' label (with : or =), spaces, and quotes
|
|
60
|
+
let sanitized = customCookie.trim();
|
|
61
|
+
// Remove "cf_clearance" prefix case-insensitively
|
|
62
|
+
sanitized = sanitized.replace(/^cf_clearance/i, '');
|
|
63
|
+
// Remove leading : or = and any whitespace
|
|
64
|
+
sanitized = sanitized.replace(/^[:=]\s*/, '');
|
|
65
|
+
// Remove all quotes and trim again
|
|
66
|
+
sanitized = sanitized.replace(/["']/g, '').trim();
|
|
67
|
+
cookieStr = `cf_clearance=${sanitized}`;
|
|
68
|
+
}
|
|
69
|
+
const headers = {
|
|
70
|
+
'User-Agent': userAgent,
|
|
71
|
+
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
|
72
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
73
|
+
Referer: `${this.BASE_URL}/`,
|
|
74
|
+
Origin: this.BASE_URL,
|
|
75
|
+
Cookie: cookieStr,
|
|
76
|
+
};
|
|
77
|
+
if (isApi) {
|
|
78
|
+
headers['X-Requested-With'] = 'XMLHttpRequest';
|
|
79
|
+
headers['Accept'] = 'application/json, text/javascript, */*; q=0.01';
|
|
80
|
+
}
|
|
81
|
+
return headers;
|
|
82
|
+
}
|
|
83
|
+
async fetchText(url, isApi = false, ua, cookie) {
|
|
84
|
+
try {
|
|
85
|
+
const headers = await this.getRequestHeaders(isApi, ua, cookie);
|
|
86
|
+
const response = await fetch(url, {
|
|
87
|
+
method: 'GET',
|
|
88
|
+
headers,
|
|
89
|
+
});
|
|
90
|
+
const text = await response.text();
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
if (response.status === 403 || text.includes('Cloudflare')) {
|
|
93
|
+
const error = new Error(`AUTH_REQUIRED`);
|
|
94
|
+
error.status = 403;
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`HTTP ${response.status}`);
|
|
98
|
+
}
|
|
99
|
+
return text;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error.message === 'AUTH_REQUIRED')
|
|
103
|
+
throw error;
|
|
104
|
+
logger_1.default.error({ url, error: error.message }, 'AnimePahe Fetch failed');
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async fetchJson(url, ua, cookie) {
|
|
109
|
+
const data = await this.fetchText(url, true, ua, cookie);
|
|
110
|
+
try {
|
|
111
|
+
return JSON.parse(data);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
logger_1.default.error({ url }, 'Failed to parse AnimePahe JSON');
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async search(options) {
|
|
119
|
+
try {
|
|
120
|
+
const q = options.query || '';
|
|
121
|
+
const url = `${this.API_URL}?m=search&q=${encodeURIComponent(q)}`;
|
|
122
|
+
const data = await this.fetchJson(url);
|
|
123
|
+
if (!data)
|
|
124
|
+
return [];
|
|
125
|
+
const items = (data.data || data.results || data.items || []);
|
|
126
|
+
return items.map((a) => ({
|
|
127
|
+
_id: a.session,
|
|
128
|
+
id: a.session,
|
|
129
|
+
name: a.title || a.name || '',
|
|
130
|
+
englishName: a.title,
|
|
131
|
+
thumbnail: a.poster || a.image,
|
|
132
|
+
type: a.type,
|
|
133
|
+
year: a.year,
|
|
134
|
+
session: a.session,
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
if (e.message === 'AUTH_REQUIRED')
|
|
139
|
+
throw e;
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async getEpisodes(showId, _mode, ua, cookie) {
|
|
144
|
+
try {
|
|
145
|
+
const firstPageUrl = `${this.API_URL}?m=release&id=${showId}&sort=episode_asc&page=1`;
|
|
146
|
+
const firstPageData = await this.fetchJson(firstPageUrl, ua, cookie);
|
|
147
|
+
if (!firstPageData)
|
|
148
|
+
return null;
|
|
149
|
+
let episodes = (firstPageData.data || firstPageData.results || []);
|
|
150
|
+
const lastPage = Number(firstPageData.last_page || firstPageData.lastPage || 1);
|
|
151
|
+
for (let p = 2; p <= lastPage; p++) {
|
|
152
|
+
const pageUrl = `${this.API_URL}?m=release&id=${showId}&sort=episode_asc&page=${p}`;
|
|
153
|
+
const pageData = await this.fetchJson(pageUrl, ua, cookie);
|
|
154
|
+
if (pageData) {
|
|
155
|
+
episodes = episodes.concat((pageData.data || pageData.results || []));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const episodeMap = {};
|
|
159
|
+
const epDetails = [];
|
|
160
|
+
episodes.forEach((ep) => {
|
|
161
|
+
const epNum = (ep.episode ?? ep.number ?? '').toString();
|
|
162
|
+
if (epNum) {
|
|
163
|
+
episodeMap[epNum] = ep.session || ep.release_session || '';
|
|
164
|
+
epDetails.push({ number: epNum, title: ep.title });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
this.cache.set(`animepahe_epmap_${showId}`, episodeMap, 86400);
|
|
168
|
+
return {
|
|
169
|
+
episodes: epDetails
|
|
170
|
+
.sort((a, b) => Number(a.number) - Number(b.number))
|
|
171
|
+
.map((e) => e.number),
|
|
172
|
+
availableEpisodesDetail: epDetails,
|
|
173
|
+
description: '',
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
if (e.message === 'AUTH_REQUIRED')
|
|
178
|
+
throw e;
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async getEpisodeSession(showId, episodeNumber) {
|
|
183
|
+
const cacheKey = `animepahe_epmap_${showId}`;
|
|
184
|
+
let cachedMap = this.cache.get(cacheKey);
|
|
185
|
+
if (!cachedMap) {
|
|
186
|
+
await this.getEpisodes(showId, 'sub');
|
|
187
|
+
cachedMap = this.cache.get(cacheKey);
|
|
188
|
+
}
|
|
189
|
+
if (!cachedMap)
|
|
190
|
+
return null;
|
|
191
|
+
if (cachedMap[episodeNumber])
|
|
192
|
+
return cachedMap[episodeNumber];
|
|
193
|
+
const target = parseFloat(episodeNumber);
|
|
194
|
+
const keys = Object.keys(cachedMap);
|
|
195
|
+
for (const key of keys) {
|
|
196
|
+
if (parseFloat(key) === target)
|
|
197
|
+
return cachedMap[key];
|
|
198
|
+
}
|
|
199
|
+
const sorted = keys.sort((a, b) => Number(a) - Number(b));
|
|
200
|
+
const first = Number(sorted[0]);
|
|
201
|
+
if (target < first) {
|
|
202
|
+
const idx = Math.floor(target) - 1;
|
|
203
|
+
if (idx >= 0 && idx < sorted.length)
|
|
204
|
+
return cachedMap[sorted[idx]];
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
async getStreamUrls(showId, episodeNumber, mode) {
|
|
209
|
+
try {
|
|
210
|
+
const epSession = await this.getEpisodeSession(showId, episodeNumber);
|
|
211
|
+
if (!epSession)
|
|
212
|
+
return null;
|
|
213
|
+
const sources = await this.getSources(showId, epSession);
|
|
214
|
+
const results = [];
|
|
215
|
+
const isDubSource = (audio) => audio.includes('eng') || audio.includes('dub');
|
|
216
|
+
for (const src of sources) {
|
|
217
|
+
const audio = (src.audio || '').toLowerCase();
|
|
218
|
+
const sourceMode = isDubSource(audio) ? 'dub' : 'sub';
|
|
219
|
+
if (sourceMode !== mode)
|
|
220
|
+
continue;
|
|
221
|
+
const label = src.fansub
|
|
222
|
+
? `${src.quality || 'Auto'} - ${src.fansub} (${sourceMode.toUpperCase()})`
|
|
223
|
+
: `${src.quality || 'Auto'} (${sourceMode.toUpperCase()})`;
|
|
224
|
+
results.push({
|
|
225
|
+
sourceName: label,
|
|
226
|
+
links: [
|
|
227
|
+
{
|
|
228
|
+
resolutionStr: src.quality || 'Auto',
|
|
229
|
+
link: `/api/embed-proxy?url=${encodeURIComponent(src.url)}`,
|
|
230
|
+
hls: false,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
type: 'iframe',
|
|
234
|
+
actualEpisodeNumber: episodeNumber,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return results.length > 0 ? results : null;
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
if (e.message === 'AUTH_REQUIRED')
|
|
241
|
+
throw e;
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async getSources(animeSession, episodeSession) {
|
|
246
|
+
try {
|
|
247
|
+
const playUrl = `${this.BASE_URL}/play/${animeSession}/${episodeSession}`;
|
|
248
|
+
const html = await this.fetchText(playUrl);
|
|
249
|
+
const $ = cheerio.load(html);
|
|
250
|
+
const sources = [];
|
|
251
|
+
$('[data-src]').each((_, el) => {
|
|
252
|
+
const src = $(el).attr('data-src')?.trim();
|
|
253
|
+
if (!src || !/kwik/i.test(src))
|
|
254
|
+
return;
|
|
255
|
+
const res = $(el).attr('data-resolution') || $(el).attr('data-res');
|
|
256
|
+
sources.push({
|
|
257
|
+
url: src,
|
|
258
|
+
quality: res ? (res.endsWith('p') ? res : `${res}p`) : null,
|
|
259
|
+
fansub: $(el).attr('data-fansub') ?? null,
|
|
260
|
+
audio: $(el).attr('data-audio') ?? null,
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
const unique = Array.from(new Map(sources.map((s) => [s.url, s])).values());
|
|
264
|
+
unique.sort((a, b) => {
|
|
265
|
+
const qa = parseInt(a.quality || '0') || 0;
|
|
266
|
+
const qb = parseInt(b.quality || '0') || 0;
|
|
267
|
+
return qb - qa;
|
|
268
|
+
});
|
|
269
|
+
return unique;
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async resolveKwik(_kwikUrl) {
|
|
276
|
+
return { m3u8: '', referer: '' };
|
|
277
|
+
}
|
|
278
|
+
async getShowMeta(showId, ua, cookie) {
|
|
279
|
+
try {
|
|
280
|
+
const url = `${this.BASE_URL}/anime/${showId}`;
|
|
281
|
+
const html = await this.fetchText(url, false, ua, cookie);
|
|
282
|
+
const $ = cheerio.load(html);
|
|
283
|
+
const metadata = {
|
|
284
|
+
_id: showId,
|
|
285
|
+
id: showId,
|
|
286
|
+
names: {},
|
|
287
|
+
};
|
|
288
|
+
const cleanText = (text) => {
|
|
289
|
+
if (!text)
|
|
290
|
+
return null;
|
|
291
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
292
|
+
};
|
|
293
|
+
const titleText = cleanText($('.anime-header h1 > span').text()) ||
|
|
294
|
+
cleanText($('.anime-header h1').text()) ||
|
|
295
|
+
'';
|
|
296
|
+
metadata.name = titleText;
|
|
297
|
+
metadata.englishName = titleText;
|
|
298
|
+
metadata.names.english = titleText;
|
|
299
|
+
const romaji = cleanText($('.anime-header h2.japanese').text());
|
|
300
|
+
if (romaji) {
|
|
301
|
+
metadata.names.romaji = romaji;
|
|
302
|
+
}
|
|
303
|
+
const posterDiv = $('.anime-poster');
|
|
304
|
+
if (posterDiv.length) {
|
|
305
|
+
const img = posterDiv.find('img');
|
|
306
|
+
if (img.length) {
|
|
307
|
+
metadata.thumbnail = img.attr('data-src') || img.attr('src');
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const synopsisDiv = $('.anime-synopsis');
|
|
311
|
+
if (synopsisDiv.length) {
|
|
312
|
+
metadata.description = cleanText(synopsisDiv.text()) || undefined;
|
|
313
|
+
}
|
|
314
|
+
const infoBox = {};
|
|
315
|
+
const infoDiv = $('.anime-info');
|
|
316
|
+
if (infoDiv.length) {
|
|
317
|
+
infoDiv.find('p').each((_, el) => {
|
|
318
|
+
const p = $(el);
|
|
319
|
+
const fullText = cleanText(p.text());
|
|
320
|
+
if (!fullText)
|
|
321
|
+
return;
|
|
322
|
+
const colonIdx = fullText.indexOf(':');
|
|
323
|
+
if (colonIdx === -1)
|
|
324
|
+
return;
|
|
325
|
+
const label = fullText.substring(0, colonIdx).trim();
|
|
326
|
+
if (label === 'External Links' || label === 'Themes' || label === 'Demographic') {
|
|
327
|
+
const items = [];
|
|
328
|
+
p.find('a').each((_, aEl) => {
|
|
329
|
+
items.push({
|
|
330
|
+
name: cleanText($(aEl).text()) || '',
|
|
331
|
+
url: $(aEl).attr('href'),
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
infoBox[label] = items;
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
const value = fullText.substring(colonIdx + 1).trim();
|
|
338
|
+
infoBox[label] = value;
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
if (typeof infoBox['Japanese'] === 'string') {
|
|
343
|
+
metadata.nativeName = infoBox['Japanese'];
|
|
344
|
+
metadata.names.native = infoBox['Japanese'];
|
|
345
|
+
}
|
|
346
|
+
if (typeof infoBox['Synonyms'] === 'string') {
|
|
347
|
+
metadata.names.synonyms = infoBox['Synonyms'].split(',').map((s) => s.trim());
|
|
348
|
+
}
|
|
349
|
+
if (typeof infoBox['Type'] === 'string') {
|
|
350
|
+
metadata.type = infoBox['Type'];
|
|
351
|
+
}
|
|
352
|
+
const epsStr = typeof infoBox['Episodes'] === 'string'
|
|
353
|
+
? infoBox['Episodes']
|
|
354
|
+
: typeof infoBox['Episode'] === 'string'
|
|
355
|
+
? infoBox['Episode']
|
|
356
|
+
: undefined;
|
|
357
|
+
if (epsStr && epsStr !== '?') {
|
|
358
|
+
const parsedEps = parseInt(epsStr, 10);
|
|
359
|
+
if (!isNaN(parsedEps)) {
|
|
360
|
+
metadata.episodeCount = parsedEps;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (typeof infoBox['Duration'] === 'string') {
|
|
364
|
+
metadata.episodeDuration = infoBox['Duration'];
|
|
365
|
+
}
|
|
366
|
+
if (typeof infoBox['Status'] === 'string') {
|
|
367
|
+
metadata.status = infoBox['Status'];
|
|
368
|
+
}
|
|
369
|
+
const parseDateStr = (dateStr) => {
|
|
370
|
+
if (!dateStr)
|
|
371
|
+
return null;
|
|
372
|
+
const parsed = Date.parse(dateStr);
|
|
373
|
+
if (isNaN(parsed))
|
|
374
|
+
return null;
|
|
375
|
+
const dateObj = new Date(parsed);
|
|
376
|
+
return {
|
|
377
|
+
year: dateObj.getFullYear(),
|
|
378
|
+
month: dateObj.getMonth(),
|
|
379
|
+
date: dateObj.getDate(),
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
const airedStr = typeof infoBox['Aired'] === 'string' ? infoBox['Aired'] : undefined;
|
|
383
|
+
if (airedStr) {
|
|
384
|
+
const dates = airedStr.split(/\s+to\s+/);
|
|
385
|
+
const start = parseDateStr(dates[0]);
|
|
386
|
+
if (start) {
|
|
387
|
+
metadata.airedStart = start;
|
|
388
|
+
}
|
|
389
|
+
if (dates[1] && dates[1] !== '?') {
|
|
390
|
+
const end = parseDateStr(dates[1]);
|
|
391
|
+
if (end) {
|
|
392
|
+
metadata.airedEnd = end;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const seasonStr = typeof infoBox['Season'] === 'string' ? infoBox['Season'] : undefined;
|
|
397
|
+
if (seasonStr) {
|
|
398
|
+
const parts = seasonStr.split(' ');
|
|
399
|
+
const seasonName = parts[0];
|
|
400
|
+
const yearMatch = seasonStr.match(/\d{4}/);
|
|
401
|
+
const yearVal = yearMatch ? parseInt(yearMatch[0], 10) : undefined;
|
|
402
|
+
if (yearVal) {
|
|
403
|
+
metadata.year = yearVal;
|
|
404
|
+
}
|
|
405
|
+
metadata.season = {
|
|
406
|
+
season: seasonName,
|
|
407
|
+
year: yearVal,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
else if (typeof infoBox['Aired'] === 'string') {
|
|
411
|
+
const yearMatch = infoBox['Aired'].match(/\d{4}/);
|
|
412
|
+
if (yearMatch) {
|
|
413
|
+
metadata.year = parseInt(yearMatch[0], 10);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (typeof infoBox['Studios'] === 'string') {
|
|
417
|
+
metadata.studios = infoBox['Studios'].split(',').map((s) => ({ name: s.trim() }));
|
|
418
|
+
}
|
|
419
|
+
const themes = infoBox['Themes'] ?? [];
|
|
420
|
+
const demographic = infoBox['Demographic'] ?? [];
|
|
421
|
+
metadata.tags = [
|
|
422
|
+
...themes.map((t) => ({ name: t.name })),
|
|
423
|
+
...demographic.map((d) => ({ name: d.name })),
|
|
424
|
+
];
|
|
425
|
+
const genreDiv = $('.anime-genre');
|
|
426
|
+
if (genreDiv.length) {
|
|
427
|
+
metadata.genres = genreDiv
|
|
428
|
+
.find('a')
|
|
429
|
+
.map((_, el) => ({ name: cleanText($(el).text()) || '' }))
|
|
430
|
+
.get();
|
|
431
|
+
}
|
|
432
|
+
return metadata;
|
|
433
|
+
}
|
|
434
|
+
catch (e) {
|
|
435
|
+
if (e.message === 'AUTH_REQUIRED')
|
|
436
|
+
throw e;
|
|
437
|
+
logger_1.default.error({ showId, error: e.message }, 'Failed to fetch AnimePahe metadata');
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async getPopular(_timeframe, _page, _size) {
|
|
442
|
+
return [];
|
|
443
|
+
}
|
|
444
|
+
async getSchedule(_date) {
|
|
445
|
+
return [];
|
|
446
|
+
}
|
|
447
|
+
async getSeasonal(_page) {
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
async getLatestReleases(_page, _size) {
|
|
451
|
+
return [];
|
|
452
|
+
}
|
|
453
|
+
async getSkipTimes(_showId, _episodeNumber) {
|
|
454
|
+
return { found: false, results: [] };
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
exports.AnimePaheProvider = AnimePaheProvider;
|