Closure-RI commited on
Commit
ed62d88
·
verified ·
1 Parent(s): 827b6ec

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +314 -837
index.js CHANGED
@@ -122,61 +122,6 @@ app.get("/", (req, res) => {
122
  res.send(keluaran);
123
  });
124
 
125
- app.post("/eval", async (req, res) => {
126
- const { code } = req.body;
127
- const { responseType = "text" } = req.query;
128
- let __dirname = path.dirname(fileURLToPath(import.meta.url));
129
- let require = createRequire(__dirname);
130
-
131
- let _return;
132
- try {
133
- _return = /await/i.test(code)
134
- ? eval("(async() => { " + code + " })()")
135
- : eval(code);
136
- } catch (e) {
137
- _return = e;
138
- }
139
-
140
- // Handle Buffer atau Base64
141
- if (Buffer.isBuffer(_return) || typeof _return === "string" && _return.startsWith("data:")) {
142
- const buffer = Buffer.isBuffer(_return)
143
- ? _return
144
- : Buffer.from(_return.split(",")[1], "base64");
145
-
146
- const fileType = await fileTypeFromBuffer(buffer);
147
- const mimeType = fileType ? fileType.mime : "application/octet-stream";
148
- const ext = fileType ? fileType.ext : "bin";
149
- const filename = `Nex - ${Date.now()}.${ext}`;
150
-
151
- res.setHeader("Content-Type", mimeType);
152
- res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
153
- return res.send(buffer);
154
- }
155
-
156
- // Handle respon berdasarkan responseType
157
- switch (responseType) {
158
- case "json":
159
- try {
160
- const jsonFormatted = typeof _return === "string" ? JSON.parse(_return) : _return;
161
- return res.json(format(jsonFormatted));
162
- } catch (err) {
163
- return res.json({ error: "Invalid JSON format", result: format(_return) });
164
- }
165
-
166
- case "file":
167
- const filePath = path.join(__dirname, `Nex - ${Date.now()}.txt`);
168
- fs.writeFileSync(filePath, _return.toString());
169
- return res.download(filePath, () => fs.unlinkSync(filePath));
170
-
171
- case "text":
172
- default:
173
- res.setHeader("Content-Type", "text/plain; charset=utf-8");
174
- return res.send(_return.toString());
175
- }
176
- });
177
-
178
-
179
-
180
 
181
 
182
  // Fungsi untuk menghasilkan IP acak
@@ -185,384 +130,6 @@ const generateRandomIP = () => {
185
  return `${octet()}.${octet()}.${octet()}.${octet()}`;
186
  };
187
 
188
- // Fungsi untuk upload file
189
- async function uploader(buffer) {
190
- const { ext } = await fileTypeFromBuffer(buffer);
191
- const bodyForm = new FormData();
192
- bodyForm.append('file', buffer, `file.${ext}`);
193
-
194
- const response = await fetch('https://aemt.me/api/upload.php', {
195
- method: 'POST',
196
- body: bodyForm,
197
- });
198
-
199
- return {
200
- status: response.status,
201
- creator: 'Nex',
202
- result: await response.json(),
203
- };
204
- }
205
-
206
- // Fungsi untuk mendapatkan URL thumbnail HD
207
- async function getHDThumbnailUrl(videoId) {
208
- try {
209
- const response = await youtube.videos.list({ part: 'snippet', id: videoId });
210
- return response.data.items[0].snippet.thumbnails.maxres.url;
211
- } catch (error) {
212
- console.error('Error fetching HD thumbnail URL:', error.message);
213
- return null;
214
- }
215
- }
216
-
217
- const getVideoDetailsWithApi = async (videoId) => {
218
- const url = `https://www.googleapis.com/youtube/v3/videos?part=snippet&id=${videoId}&key=AIzaSyBPkpdJEGtAHebbaP3_CcA1_urfMFfeLLg`;
219
-
220
- try {
221
- const response = await axios.get(url);
222
- const video = response.data.items[0];
223
-
224
- const title = video.snippet.title;
225
- const description = video.snippet.description;
226
- const thumbnail = video.snippet.thumbnails.high.url;
227
- const channelTitle = video.snippet.channelTitle;
228
- const publishedAt = video.snippet.publishedAt;
229
- const tags = video.snippet.tags;
230
-
231
- const videoDetails = {
232
- title: title,
233
- description: description,
234
- thumbnail: thumbnail,
235
- channelTitle: channelTitle,
236
- publishedAt: publishedAt,
237
- tags: tags,
238
- };
239
-
240
- return videoDetails
241
- } catch (error) {
242
- console.error('An error occurred:', error);
243
- }
244
- };
245
-
246
- // Fungsi untuk mendapatkan ID video dari URL
247
- async function GetId(data) {
248
- const regex = /(?:https?:\/\/)?(?:www\.)?(?:youtu(?:be\.com\/(?:watch\?(?:v=|vi=)|v\/|vi\/)|\.be\/|be\.com\/embed\/|be\.com\/shorts\/)|youtube\.com\/\?(?:v=|vi=))([\w-]{11})/;
249
- const res = regex.exec(data);
250
- if (res && res[1]) return res[1];
251
- throw new Error("Please check the URL you have entered");
252
- }
253
-
254
- // Fungsi untuk menambahkan tag ID3 ke file audio
255
- async function addAudioTags(media, title, artist, year, imagecover) {
256
- try {
257
- let audioBuffer;
258
- if (typeof media === 'string') {
259
- const response = await axios.get(media, { responseType: 'arraybuffer', maxContentLength: -1 });
260
- audioBuffer = Buffer.from(response.data);
261
- } else if (media instanceof Buffer) {
262
- audioBuffer = media;
263
- } else {
264
- throw new Error('Media harus berupa URL string atau Buffer.');
265
- }
266
-
267
- const randomFilename = generateRandomName(10) + '.mp3';
268
- const tmpFilePath = path.join(tempDir, randomFilename);
269
- fs.writeFileSync(tmpFilePath, audioBuffer);
270
-
271
- const tags = { title, artist, year };
272
- if (typeof imagecover === 'string') {
273
- const response = await axios.get(imagecover, { responseType: 'arraybuffer' });
274
- const coverBuffer = Buffer.from(response.data);
275
- tags.image = {
276
- mime: 'image/jpeg',
277
- type: { id: 3, name: 'Front Cover' },
278
- description: 'Cover',
279
- imageBuffer: coverBuffer
280
- };
281
- } else if (imagecover instanceof Buffer) {
282
- tags.image = {
283
- mime: 'image/jpeg',
284
- type: { id: 3, name: 'Front Cover' },
285
- description: 'Cover',
286
- imageBuffer: imagecover
287
- };
288
- }
289
-
290
- const success = nodeID3.write(tags, tmpFilePath);
291
- console[success ? 'log' : 'error'](success ? 'Tag ID3 berhasil diubah!' : 'Gagal mengubah tag ID3.');
292
-
293
- return { msg: `Audio berhasil diubah.`, path: `${tmpFilePath}` };
294
- } catch (error) {
295
- console.error('Terjadi kesalahan:', error);
296
- throw new Error('Terjadi kesalahan saat mengubah audio.');
297
- }
298
- }
299
-
300
- // Fungsi untuk menghasilkan nama acak
301
- function generateRandomName(length) {
302
- const characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
303
- let randomName = '';
304
- for (let i = 0; i < length; i++) {
305
- randomName += characters.charAt(Math.floor(Math.random() * characters.length));
306
- }
307
- return randomName;
308
- }
309
-
310
-
311
- async function fetchCobaltOnly(url, opts = {}) {
312
- try {
313
- const response = await axios.post('https://c.blahaj.ca/', { url, ...opts }, { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } } );
314
- return response.data;
315
- } catch (error) {
316
- if (error.response) {
317
- const contentType = error.response.headers['content-type'];
318
- if (contentType && contentType.includes('json')) {
319
- throw error.response.data.message || 'An error occurred';
320
- }
321
- throw error.response.statusText;
322
- }
323
- throw error.message;
324
- }
325
- }
326
-
327
-
328
-
329
- const servers = [
330
- "https://cobalt.siputzx.my.id/",
331
- "https://c.blahaj.ca/"
332
- ];
333
-
334
- async function cobalt(config) {
335
- try {
336
- if (!(typeof config === "object")) {
337
- throw new Error("Invalid config input, config must be a JSON object!");
338
- }
339
-
340
- config = {
341
- url: config?.url || null,
342
- videoQuality: config?.videoQuality || "720",
343
- audioFormat: config?.audioFormat || "mp3",
344
- audioBitrate: config?.audioBitrate || "128",
345
- filenameStyle: config?.filenameStyle || "classic",
346
- downloadMode: config?.downloadMode || "auto",
347
- youtubeVideoCodec: config?.youtubeVideoCodec || "h264",
348
- youtubeDubLang: config?.youtubeDubLang || "en",
349
- alwaysProxy: config?.alwaysProxy || false,
350
- disableMetadata: config?.disableMetadata || false,
351
- tiktokFullAudio: config?.tiktokFullAudio || true,
352
- tiktokH265: config?.tiktokH265 || true,
353
- twitterGif: config?.twitterGif || true,
354
- youtubeHLS: config?.youtubeHLS || false,
355
- };
356
-
357
- if (!config.url) {
358
- throw new Error("Missing URL input!");
359
- }
360
-
361
- for (let i = 0; i < servers.length; i++) {
362
- try {
363
- console.log(`Trying server: ${servers[i]}`); // Log server yang dicoba
364
- const response = await axios.post(servers[i], config, {
365
- headers: {
366
- accept: "application/json",
367
- contentType: "application/json",
368
- },
369
- });
370
-
371
- const data = response.data;
372
- if (data.status === "error") {
373
- throw new Error("Failed to fetch content from server.");
374
- }
375
-
376
- console.log(`Success with server: ${servers[i]}`); // Log server sukses
377
- return {
378
- success: true,
379
- result: data,
380
- };
381
- } catch (error) {
382
- if (i === servers.length - 1) {
383
- // Jika sudah mencoba semua server
384
- throw error;
385
- }
386
- console.warn(`Server ${servers[i]} failed. Trying next server...`); // Log server gagal
387
- }
388
- }
389
- } catch (error) {
390
- return {
391
- success: false,
392
- errors: error.message || error,
393
- };
394
- }
395
- }
396
-
397
-
398
-
399
- async function getAudioMP3Url(videoUrl) {
400
- try {
401
- const id_video = await GetId(videoUrl);
402
- const infoVids = await getVideoDetailsWithApi(id_video);
403
-
404
- const video = await fetchCobaltOnly(videoUrl, { downloadMode: "audio", audioBitrate: "128", filenameStyle: "pretty", audioFormat: "mp3"})
405
- console.log(video)
406
-
407
- // Unduh file audio terlebih dahulu
408
- /*
409
- const video = await cobalt({
410
- url: videoUrl,
411
- downloadMode: "audio",
412
- filenameStyle: "pretty",
413
- audioFormat: "mp3",
414
- audioBitrate: "128"
415
- })
416
- */
417
- const path_audio = path.join(tempDir, generateRandomName(10) + '.mp3');
418
- const path_audio_edit = path_audio.replace('.mp3', '_edit.mp3');
419
-
420
- // Download file audio
421
- const headers = {
422
- 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Mobile Safari/537.36',
423
- 'Referer': 'https://cobalt.tools/pustaka/'
424
- };
425
- const response = await axios.get(video.url, { responseType: 'arraybuffer', headers });
426
- fs.writeFileSync(path_audio, response.data);
427
-
428
- // Periksa apakah file input valid
429
- if (!fs.existsSync(path_audio)) {
430
- throw new Error('File audio tidak ditemukan setelah diunduh.');
431
- }
432
- if (fs.statSync(path_audio).size === 0) {
433
- throw new Error('File audio kosong atau rusak.');
434
- }
435
-
436
- // Debugging tambahan: coba cek format file
437
- console.log(`File audio diunduh: ${path_audio}, size: ${fs.statSync(path_audio).size} bytes`);
438
-
439
- // Tambahkan metadata ke file yang diunduh
440
- await new Promise((resolve, reject) => {
441
- ffmpeg(path_audio)
442
- .outputOptions(['-acodec libmp3lame', '-ab 128k', '-ar 44100'])
443
- .on('start', (commandLine) => {
444
- console.log('FFmpeg command:', commandLine); // Log perintah FFmpeg
445
- })
446
- .on('stderr', (stderrLine) => {
447
- console.error('FFmpeg stderr:', stderrLine); // Log error FFmpeg
448
- })
449
- .on('end', async () => {
450
- try {
451
- // Tambahkan metadata
452
- const buffer = fs.readFileSync(path_audio_edit); // Ambil file hasil edit
453
- const edited = await addAudioTags(buffer, infoVids.title, infoVids.channelTitle, 2024, infoVids.thumbnail);
454
-
455
- // Ganti file lama dengan file yang sudah diedit
456
- const buffer2 = fs.readFileSync(edited.path);
457
- fs.writeFileSync(path_audio, buffer2);
458
- fs.unlinkSync(path_audio_edit); // Hapus file sementara
459
-
460
- resolve();
461
- } catch (error) {
462
- reject(error);
463
- }
464
- })
465
- .on('error', (err) => {
466
- console.error('FFmpeg conversion error:', err);
467
- reject(err);
468
- })
469
- .save(path_audio_edit); // Simpan sementara dengan nama _edit.mp3
470
- });
471
-
472
- // Kembalikan hasil akhir
473
- return {
474
- status: 200,
475
- title: infoVids.title,
476
- result: {
477
- fileName: video.filename,
478
- url_path: `https://${process.env.SPACE_HOST}/temp/${path.basename(path_audio)}`,
479
- curl_path: `https://${process.env.SPACE_HOST}/temp/${path.basename(path_audio)}?download=1&filename=${infoVids.title}`,
480
- path: path_audio
481
- },
482
- infoVids
483
- };
484
- } catch (error) {
485
- console.error('Error:', error);
486
- throw new Error('Failed to process audio URL');
487
- }
488
- }
489
-
490
-
491
-
492
-
493
-
494
-
495
-
496
- app.get('/ytmp3', async (req, res) => {
497
- try {
498
- const { url } = req.query;
499
- if (!url) return res.status(400).json({ error: 'Parameter url is required' });
500
-
501
- let result = await getAudioMP3Url(url);
502
- res.json(result);
503
-
504
- // Menghapus file setelah 10 menit
505
- try {
506
- await new Promise(resolve => setTimeout(resolve, 10 * 60 * 1000)); // 10 minutes
507
- await fss.unlink(result.result.path);
508
- console.log(`File ${result.result.path} deleted.`);
509
- } catch (error) {
510
- console.error(`Error deleting file ${result.result.path}:`, error);
511
- }
512
- } catch (error) {
513
- console.error('Error processing request:', error);
514
- res.status(500).json({
515
- error: 'Failed to process request\n' + error
516
- });
517
- }
518
- });
519
-
520
-
521
-
522
- async function fetchHtml(url) {
523
- // Launch browser dengan mode headless
524
- const browser = await puppeteer.launch();
525
-
526
- // Buat page baru
527
- const page = await browser.newPage();
528
-
529
- // Set User Agent untuk menghindari deteksi bot
530
- //await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.3');
531
-
532
- await page.setUserAgent("Mozilla/5.0 (Linux; Android 10; SM-G965U Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.141 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/420.0.0.32.61;]");
533
-
534
- // Navigasi ke URL yang diinginkan
535
- //await page.goto(url);
536
-
537
- await page.goto(url, { waitUntil: 'networkidle2' });
538
-
539
- // Tunggu sampai page selesai loading
540
- //await page.waitForNavigation({ waitUntil: 'networkidle2' });
541
-
542
- // Ambil HTML dari page
543
- const html = await page.content();
544
-
545
- // Tutup browser
546
- await browser.close();
547
-
548
- // Return HTML
549
- return html;
550
- }
551
-
552
- app.get('/html', async (req, res) => {
553
- try {
554
- const { url } = req.query;
555
- if (!url) return res.status(400).json({ error: 'Parameter url is required' });
556
- let result = await fetchHtml(url);
557
- res.send(result);
558
- } catch (error) {
559
- console.error('Error processing request:', error);
560
- res.status(500).json({
561
- error: 'Failed to process request\n' + error
562
- });
563
- }
564
- });
565
-
566
 
567
  async function XnDl(url) {
568
  const browser = await puppeteer.launch({
@@ -651,410 +218,6 @@ function generateRandomID(length = 8) {
651
  return result;
652
  }
653
 
654
- async function komiku_download(url) {
655
- const instanceID = generateRandomID();
656
- const tempDir = path.join(tempDirBase, instanceID);
657
- await fss.mkdir(tempDir);
658
-
659
- // Extracting the title from the URL
660
- const title = url.split('/').filter(part => part).pop();
661
-
662
- try {
663
- const response = await axios.get(url);
664
- const html = response.data;
665
- const $ = cheerio.load(html);
666
- const imgList = [];
667
-
668
- $('#Baca_Komik img').each((index, element) => {
669
- const src = $(element).attr('src');
670
- imgList.push({ path: src });
671
- });
672
-
673
- await processImages(imgList, tempDir, instanceID);
674
- const pdfPath = await createPDF(instanceID, tempDir);
675
-
676
- console.log(`PDF berhasil dibuat: ${pdfPath}`);
677
- return { path: pdfPath, title: title, url: `https://${process.env.SPACE_HOST}/temp/${path.basename(pdfPathq)}` };
678
- } catch (error) {
679
- console.log(error);
680
- throw error;
681
- } finally {
682
- await fss.rmdir(tempDir, { recursive: true });
683
- }
684
- }
685
-
686
- async function downloadImage(image, tempDir, instanceID) {
687
- const response = await axios.get(image.path, { responseType: 'arraybuffer' });
688
- const imagePath = path.join(tempDir, `image_${instanceID}_${Date.now()}_${Math.floor(Math.random() * 1000)}.jpg`);
689
- await writeFileAsync(imagePath, response.data);
690
-
691
- const imageHeight = await getImageHeight(imagePath);
692
- const newHeight = Math.floor(imageHeight * 0.7);
693
- const command = `convert ${imagePath} -resize 720x${newHeight}! -quality 75 -background white -gravity center -extent 720x${newHeight} ${imagePath}`;
694
- await execPromise(command);
695
-
696
- return imagePath;
697
- }
698
-
699
- async function getImageHeight(imagePath) {
700
- const { stdout } = await execPromise(`identify -format "%h" ${imagePath}`);
701
- return parseInt(stdout.trim());
702
- }
703
-
704
- async function processImages(imgList, tempDir, instanceID) {
705
- const maxImagesPerPage = 10; // Maksimal 10 gambar per halaman
706
- let partIndex = 0;
707
- let partImages = [];
708
-
709
- for (let i = 0; i < imgList.length; i++) {
710
- const imagePath = await downloadImage(imgList[i], tempDir, instanceID);
711
- partImages.push(imagePath);
712
-
713
- if (partImages.length >= maxImagesPerPage) {
714
- await combineAndSave(partImages, partIndex, tempDir, instanceID);
715
- partImages = [];
716
- partIndex++;
717
- }
718
- }
719
-
720
- // Jika masih ada gambar yang belum diproses
721
- if (partImages.length > 0) {
722
- await combineAndSave(partImages, partIndex, tempDir, instanceID);
723
- }
724
- }
725
-
726
- async function combineAndSave(imagePaths, partIndex, tempDir, instanceID) {
727
- const combinedImagePath = path.join(tempDir, `combined_part_${instanceID}_${partIndex}.jpg`);
728
- const command = `convert ${imagePaths.join(' ')} -append -quality 75 ${combinedImagePath}`;
729
- await execPromise(command);
730
-
731
- imagePaths.forEach(fs.unlinkSync);
732
-
733
- return combinedImagePath;
734
- }
735
-
736
- async function createPDF(instanceID, tempDir) {
737
- const combinedParts = fs.readdirSync(tempDir).filter(file => file.startsWith(`combined_part_${instanceID}_`));
738
- const combinedImagesPath = combinedParts.map(file => path.join(tempDir, file)).join(' ');
739
-
740
- const pdfPath = path.join(tempDir, `${instanceID}.pdf`);
741
- const createPDFCommand = `convert ${combinedImagesPath} ${pdfPath}`;
742
- await execPromise(createPDFCommand);
743
-
744
- return pdfPath;
745
- }
746
-
747
- app.get('/komiku/download', async (req, res) => {
748
- try {
749
- const { url } = req.query;
750
- if (!url) return res.status(400).json({ error: 'Parameter url is required' });
751
-
752
- let result = await komiku_download(url);
753
- res.json(result);
754
-
755
- // Menghapus file setelah 10 menit
756
- try {
757
- await new Promise(resolve => setTimeout(resolve, 10 * 60 * 1000)); // 10 minutes
758
- await fss.unlink(result.path);
759
- console.log(`File ${result.path} deleted.`);
760
- } catch (error) {
761
- console.error(`Error deleting file ${result.path}:`, error);
762
- }
763
- } catch (error) {
764
- console.error('Error processing request:', error);
765
- res.status(500).json({
766
- error: 'Failed to process request\n' + error
767
- });
768
- }
769
- });
770
- /*
771
- V2 V2 V2 V2
772
- */
773
-
774
- async function komiku_downloadV2(url) {
775
- const instanceID = generateRandomID();
776
- const tempDir = path.join(tempDirBase, instanceID);
777
- await fss.mkdir(tempDir);
778
-
779
- // Extracting the title from the URL
780
- const title = url.split('/').filter(part => part).pop();
781
-
782
- let browser;
783
- try {
784
- browser = await puppeteer.launch({
785
- headless: true,
786
- args: ['--no-sandbox', '--disable-setuid-sandbox']
787
- });
788
- const page = await browser.newPage();
789
- await page.goto(url, { waitUntil: 'networkidle2' });
790
-
791
- // Extracting images from the page
792
- const imgList = await page.evaluate(() => {
793
- return Array.from(document.querySelectorAll('#Baca_Komik img')).map(img => img.src);
794
- });
795
-
796
- const images = imgList.map(src => ({ path: src }));
797
-
798
- await processImagesV2(images, tempDir, instanceID);
799
- const pdfPath = await createPDFV2(instanceID, tempDir);
800
-
801
- console.log(`PDF berhasil dibuat: ${pdfPath}`);
802
- return { path: pdfPath, title: title, url: `https://${process.env.SPACE_HOST}/temp/${path.basename(pdfPath)}` };
803
- } catch (error) {
804
- console.log(error);
805
- throw error;
806
- } finally {
807
- if (browser) {
808
- await browser.close();
809
- }
810
- await fss.rmdir(tempDir, { recursive: true });
811
- }
812
- }
813
-
814
- async function downloadImageV2(image, tempDir, instanceID) {
815
- const response = await puppeteer.download(image.path, { responseType: 'arraybuffer' });
816
- const imagePath = path.join(tempDir, `image_${instanceID}_${Date.now()}_${Math.floor(Math.random() * 1000)}.jpg`);
817
- await writeFileAsync(imagePath, response);
818
-
819
- const imageHeight = await getImageHeightV2(imagePath);
820
- const newHeight = Math.floor(imageHeight * 0.7);
821
- const command = `convert ${imagePath} -resize 720x${newHeight}! -quality 75 -background white -gravity center -extent 720x${newHeight} ${imagePath}`;
822
- await execPromise(command);
823
-
824
- return imagePath;
825
- }
826
-
827
- async function getImageHeightV2(imagePath) {
828
- const { stdout } = await execPromise(`identify -format "%h" ${imagePath}`);
829
- return parseInt(stdout.trim());
830
- }
831
-
832
- async function processImagesV2(imgList, tempDir, instanceID) {
833
- const maxImagesPerPage = 10; // Maksimal 10 gambar per halaman
834
- let partIndex = 0;
835
- let partImages = [];
836
-
837
- for (let i = 0; i < imgList.length; i++) {
838
- const imagePath = await downloadImageV2(imgList[i], tempDir, instanceID);
839
- partImages.push(imagePath);
840
-
841
- if (partImages.length >= maxImagesPerPage) {
842
- await combineAndSaveV2(partImages, partIndex, tempDir, instanceID);
843
- partImages = [];
844
- partIndex++;
845
- }
846
- }
847
-
848
- // Jika masih ada gambar yang belum diproses
849
- if (partImages.length > 0) {
850
- await combineAndSaveV2(partImages, partIndex, tempDir, instanceID);
851
- }
852
- }
853
-
854
- async function combineAndSaveV2(imagePaths, partIndex, tempDir, instanceID) {
855
- const combinedImagePath = path.join(tempDir, `combined_part_${instanceID}_${partIndex}.jpg`);
856
- const command = `convert ${imagePaths.join(' ')} -append -quality 75 ${combinedImagePath}`;
857
- await execPromise(command);
858
-
859
- imagePaths.forEach(fs.unlinkSync);
860
-
861
- return combinedImagePath;
862
- }
863
-
864
- async function createPDFV2(instanceID, tempDir) {
865
- const combinedParts = fs.readdirSync(tempDir).filter(file => file.startsWith(`combined_part_${instanceID}_`));
866
- const combinedImagesPath = combinedParts.map(file => path.join(tempDir, file)).join(' ');
867
-
868
- const pdfPath = path.join(tempDir, `${instanceID}.pdf`);
869
- const createPDFCommand = `convert ${combinedImagesPath} ${pdfPath}`;
870
- await execPromise(createPDFCommand);
871
-
872
- return pdfPath;
873
- }
874
-
875
-
876
- app.get('/komiku/downloadV2', async (req, res) => {
877
- try {
878
- const { url } = req.query;
879
- if (!url) return res.status(400).json({ error: 'Parameter url is required' });
880
- let result = await komiku_downloadV2(url);
881
- res.json(result);
882
-
883
- // Menghapus file setelah 10 menit
884
- try {
885
- await new Promise(resolve => setTimeout(resolve, 10 * 60 * 1000)); // 10 minutes
886
- await fss.unlink(result.path);
887
- console.log(`File ${result.path} deleted.`);
888
- } catch (error) {
889
- console.error(`Error deleting file ${result.path}:`, error);
890
- }
891
- } catch (error) {
892
- console.error('Error processing request:', error);
893
- res.status(500).json({
894
- error: 'Failed to process request\n' + error
895
- });
896
- }
897
- });
898
- /***********/
899
-
900
- async function getLatestKomik(page) {
901
- const url = `https://api.komiku.id/manga/page/${page}/`;
902
- const headers = {
903
- 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Mobile Safari/537.36',
904
- 'Referer': 'https://komiku.id/pustaka/'
905
- };
906
-
907
- try {
908
- const response = await axios.get(url, { headers });
909
- const $ = cheerio.load(response.data);
910
- const mangaArray = [];
911
-
912
- // Scraping data
913
- $('.bge').each((index, element) => {
914
- const title = $(element).find('.kan h3').text().trim();
915
- const link_komik = $(element).find('.bgei a').attr('href');
916
- const imgSrc = $(element).find('.bgei img').attr('src');
917
- const type = $(element).find('.tpe1_inf b').text().trim();
918
- const type2 = $(element).find('.tpe1_inf').text().trim();
919
- const description = $(element).find('.kan p').text().trim();
920
- const readersInfo = $(element).find('.judul2').text().trim();
921
- const latestChapter = "https://komiku.id" + $(element).find('.new1:last-child a').attr('href');
922
-
923
- mangaArray.push({
924
- title,
925
- link_komik,
926
- imgSrc,
927
- type,
928
- type2,
929
- description,
930
- readersInfo,
931
- latestChapter
932
- });
933
- });
934
-
935
- return mangaArray;
936
- } catch (error) {
937
- console.error('Error fetching the URL', error);
938
- throw error;
939
- }
940
- }
941
-
942
- async function GetKomik(url) {
943
- try {
944
- const response = await axios.get(url);
945
- const $ = cheerio.load(response.data);
946
-
947
- const cover = $('#Informasi > div > img').attr('src');
948
- const judul = $('#Informasi > table > tbody > tr:nth-child(1) > td:nth-child(2)').text().trim();
949
- const jenis = $('#Informasi > table > tbody > tr:nth-child(3) > td:nth-child(2) > b').text().trim();
950
- const konsepCerita = $('#Informasi > table > tbody > tr:nth-child(4) > td:nth-child(2)').text().trim();
951
- const author = $('#Informasi > table > tbody > tr:nth-child(5) > td:nth-child(2)').text().trim();
952
- const status = $('#Informasi > table > tbody > tr:nth-child(6) > td:nth-child(2)').text().trim();
953
- const sinopsis = $('#Judul > p.desc').text().trim();
954
-
955
- const genreElements = $('#Informasi > ul > li').map((i, el) => $(el).text().trim()).get();
956
-
957
- const chapterElements = $('#daftarChapter > tr').map((i, el) => {
958
- if (i === 0) {
959
- return null;
960
- }
961
- return {
962
- judulSeries: $(el).find('td.judulseries > a').text().trim(),
963
- tanggalUpdate: $(el).find('td.tanggalseries').text().trim(),
964
- url: "https://komiku.id" + $(el).find('td.judulseries > a').attr('href')
965
- };
966
- }).get().filter(chapter => chapter !== null);
967
-
968
- const mangaInfo = {
969
- cover,
970
- judul,
971
- sinopsis,
972
- jenis,
973
- konsepCerita,
974
- author,
975
- status,
976
- genres: genreElements,
977
- chapters: chapterElements
978
- };
979
-
980
- return mangaInfo;
981
- } catch (error) {
982
- console.error('Error fetching the URL', error);
983
- throw error;
984
- }
985
- }
986
-
987
-
988
- app.get('/komiku/latest', async (req, res) => {
989
- try {
990
- const { page } = req.query;
991
- if (!page) return res.status(400).json({ error: 'Parameter page is required' });
992
-
993
- let result = await getLatestKomik(page);
994
- res.json(result);
995
- } catch (error) {
996
- console.error('Error processing request:', error);
997
- res.status(500).json({
998
- error: 'Failed to process request\n' + error
999
- });
1000
- }
1001
- });
1002
-
1003
- app.get('/komiku', async (req, res) => {
1004
- try {
1005
- const { url } = req.query;
1006
- if (!url) return res.status(400).json({ error: 'Parameter url is required' });
1007
-
1008
- let result = await GetKomik(url);
1009
- res.json(result);
1010
- } catch (error) {
1011
- console.error('Error processing request:', error);
1012
- res.status(500).json({
1013
- error: 'Failed to process request\n' + error
1014
- });
1015
- }
1016
- });
1017
-
1018
-
1019
- app.post("/cobalt", async (req, res) => {
1020
- const config = req.body;
1021
- try {
1022
- if (!config.url) {
1023
- return res.status(400).json({
1024
- success: false,
1025
- message: "Missing 'url' in the request body.",
1026
- });
1027
- }
1028
- const result = await cobalt(config);
1029
- if (result.success) {
1030
- return res.status(200).json(result);
1031
- } else {
1032
- return res.status(500).json({
1033
- success: false,
1034
- message: "Failed to process the request.",
1035
- errors: result,
1036
- });
1037
- }
1038
- } catch (error) {
1039
- res.status(500).json({
1040
- success: false,
1041
- message: "Internal server error.",
1042
- errors: error || error.message,
1043
- });
1044
- }
1045
- });
1046
-
1047
-
1048
- /*******************
1049
- ┏┓┏━┓┏━━━┓┏━┓┏━┓┏━━┓┏┓┏━┓┏┓╋┏┓
1050
- ┃┃┃┏┛┃┏━┓┃┃┃┗┛┃┃┗┫┣┛┃┃┃┏┛┃┃╋┃┃
1051
- ┃┗┛┛╋┃┃╋┃┃┃┏┓┏┓┃╋┃┃╋┃┗┛┛╋┃┃╋┃┃
1052
- ┃┏┓┃╋┃┃╋┃┃┃┃┃┃┃┃╋┃┃╋┃┏┓┃╋┃┃╋┃┃
1053
- ┃┃┃┗┓┃┗━┛┃┃┃┃┃┃┃┏┫┣┓┃┃┃┗┓┃┗━┛┃
1054
- ┗┛┗━┛┗━━━┛┗┛┗┛┗┛┗━━┛┗┛┗━┛┗━━━┛
1055
- *********************/
1056
-
1057
-
1058
  async function iwaraDL(url) {
1059
  const browser = await puppeteer.launch({
1060
  headless: true,
@@ -1150,6 +313,320 @@ app.get('/xnxx/download', async (req, res) => {
1150
  }
1151
  });
1152
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1153
  /*******************************/
1154
  // Fungsi untuk ping website
1155
  async function pingWebsite() {
 
122
  res.send(keluaran);
123
  });
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
 
127
  // Fungsi untuk menghasilkan IP acak
 
130
  return `${octet()}.${octet()}.${octet()}.${octet()}`;
131
  };
132
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
  async function XnDl(url) {
135
  const browser = await puppeteer.launch({
 
218
  return result;
219
  }
220
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  async function iwaraDL(url) {
222
  const browser = await puppeteer.launch({
223
  headless: true,
 
313
  }
314
  });
315
 
316
+ class Xnxx {
317
+ constructor() {
318
+ }
319
+
320
+ async search(nama_porn, page) {
321
+ let urls = `https://www.xnxx.com/search/${nama_porn}`;
322
+ if (this.page > 0) {
323
+ urls += `/${Number(page) - 1}`;
324
+ }
325
+
326
+ const ress = await axios.get(urls);
327
+ const $ = cheerio.load(ress.data);
328
+
329
+ let dataVideo = [];
330
+ const videoPromises = [];
331
+
332
+ $('div.thumb-block').each((i, element) => {
333
+ const judul = $(element).find('p a').attr('title');
334
+ const url = $(element).find('p a').attr('href');
335
+ const thumbnail = $(element).find('div.thumb-inside div.thumb a img').attr('data-src') || $(element).find('p a img').attr('src');
336
+ const views = $(element).find('.metadata .right').text().trim();
337
+ const durasi = $(element).find('.metadata').contents().filter(function() {
338
+ return this.nodeType === 3;
339
+ }).text().trim();
340
+
341
+ const urlVideo = `https://www.xnxx.com${url}`;
342
+ videoPromises.push(
343
+ axios.get(urlVideo).then((resss) => {
344
+ const $$ = cheerio.load(resss.data);
345
+
346
+ const detailJudul = $$('#video-content-metadata .video-title strong').text().trim() || judul;
347
+ const detailDurasiMatch = $$('#video-content-metadata .metadata').text().match(/(\d+min \d+sec|\d+sec)/);
348
+ const detailDurasi = detailDurasiMatch ? detailDurasiMatch[0] : durasi;
349
+
350
+ const detailViewsMatch = $$('#video-content-metadata .metadata').html().match(/(\d+(?:,\d+)?)\s*<span class="icon-f icf-eye"><\/span>/);
351
+ const detailViews = detailViewsMatch ? detailViewsMatch[1].replace(/,/g, '') : views;
352
+
353
+ const detailRating = $$('#video-votes .rating-box.value').text().trim();
354
+ const jumlahKomentar = $$('#tabComments .nb-video-comments').text().trim();
355
+ const tag = $$('.video-tags a.is-keyword').map((_, tag) => $$(tag).text()).get().join(', ');
356
+
357
+ dataVideo.push({
358
+ judul: detailJudul,
359
+ thumbnail: thumbnail,
360
+ url: urlVideo,
361
+ views: detailViews,
362
+ durasi: detailDurasi,
363
+ rating: detailRating,
364
+ jumlahKomentar: jumlahKomentar,
365
+ tag: tag
366
+ });
367
+ })
368
+ );
369
+ });
370
+
371
+ await Promise.all(videoPromises);
372
+
373
+ let next = $("#content-thumbs > div:nth-child(4) > ul > li")
374
+ .find("a.no-page")
375
+ .attr('href')?.match(/\/(\d+)$/)?.[1] || null;
376
+ return { next, dataVideo };
377
+ }
378
+
379
+ async download(videoURL) {
380
+ try {
381
+ const ress = await axios.get("https://arashicode-api.hf.space/xnxx/download?url=" + videoURL);
382
+ return ress.data;
383
+ } catch (err) {
384
+ throw new Error(`Gagal mengambil link download: ${err}`);
385
+ }
386
+ }
387
+ }
388
+
389
+ app.get('/xnxx/search', async (req, res) => {
390
+ const { query, page } = req.query;
391
+ if (!query) return res.status(400).json({ error: 'Parameter query is required' });
392
+ try {
393
+ const xnxx = new Xnxx();
394
+ let result = await xnxx.search(query, page);
395
+ res.json(result); // Mengirimkan buffer gambar sebagai respons
396
+ } catch (error) {
397
+ res.status(500).send(error);
398
+ }
399
+ });
400
+
401
+ /**********************************************************************************/
402
+
403
+ class XvideosScraper {
404
+ constructor() {
405
+ this.baseURL = 'https://www.xvideos.com';
406
+ this.browser = null;
407
+ this.page = null;
408
+ }
409
+ async initialize() {
410
+ this.browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
411
+ this.page = await this.browser.newPage();
412
+ await this.page.setUserAgent("Mozilla/5.0 (Linux; Android 10; SM-G965U Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.141 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/420.0.0.32.61;]");
413
+ }
414
+
415
+ async search(query, pageNumber) {
416
+ const url = `${this.baseURL}/?k=${encodeURIComponent(query)}`;
417
+ if(pageNumber > 0) {
418
+ url += `&p=${pageNumber}`;
419
+ }
420
+ console.log(`Navigating to: ${url}`);
421
+ await this.page.goto(url, { waitUntil: 'networkidle2' });
422
+
423
+ const result = await this.page.evaluate(() => {
424
+ function generateVideoUrl(url, use169, replaceJpgWithMp4) {
425
+ if (replaceJpgWithMp4) {
426
+ return url.replace(".jpg", ".mp4");
427
+ }
428
+ let newUrl = url.substring(0, url.lastIndexOf("/"))
429
+ .replace(/\/thumbs(169)?(xnxx)?((l*)|(poster))\//, "/videopreview/");
430
+ newUrl += use169 ? "_169.mp4" : "_43.mp4";
431
+ newUrl = newUrl.replace(/(-[0-9]+)_([0-9]+)/, "_$2$1");
432
+ return newUrl;
433
+ }
434
+
435
+ const elements = document.querySelectorAll('#content > div.mozaique > div');
436
+ const next = document.querySelector("#content > div.pagination > ul > li:last-child > a")?.href || null;
437
+
438
+ const data = Array.from(elements).map(el => {
439
+ const link = el.querySelector('div.thumb-inside > div > a')?.href || null;
440
+ const image = el.querySelector('div.thumb-inside > div > a > img')?.src || null;
441
+ const title = el.querySelector('div.thumb-under > p.title > a')?.getAttribute("title") || '';
442
+ const duration = el.querySelector('div.thumb-under > p.metadata > span > span.duration')?.textContent.trim() || '';
443
+ const quality = el.querySelector('div.thumb-inside > div > a > span')?.textContent.trim() || '';
444
+ const uploader = el.querySelector('div.thumb-under > p.metadata > span > span:nth-child(2) > a > span')?.textContent.trim() || '';
445
+ const idMatch = el.querySelector('script')?.innerHTML.match(/xv\.thumbs\.prepareVideo\((\d+)\)/);
446
+ const id = idMatch ? idMatch[1] : '';
447
+ const preview = image ? generateVideoUrl(image, true, false) : '';
448
+
449
+ return { link, image, title, duration, quality, uploader, id, preview };
450
+ }).filter(item => item.title.length >= 1);
451
+
452
+ return { next, data };
453
+ });
454
+
455
+ return result;
456
+ }
457
+
458
+ async close() {
459
+ await this.browser.close();
460
+ }
461
+ async download(videoURL) {
462
+ try {
463
+ const ress = await axios.get("https://arashicode-api.hf.space/xnxx/download?url=" + videoURL);
464
+ return ress.data;
465
+ } catch (err) {
466
+ throw new Error(`Gagal mengambil link download: ${err}`);
467
+ }
468
+ }
469
+ }
470
+
471
+ app.get('/xvideos/search', async (req, res) => {
472
+ const { query, page } = req.query;
473
+ if (!query) return res.status(400).json({ error: 'Parameter query is required' });
474
+ try {
475
+ const scraper = new XvideosScraper();
476
+ await scraper.initialize();
477
+ let result = await scraper.search(query, page);
478
+ res.json(result); // Mengirimkan buffer gambar sebagai respons
479
+ } catch (error) {
480
+ res.status(500).send(error);
481
+ }
482
+ });
483
+
484
+ app.get('/xvideos/download', async (req, res) => {
485
+ const { url } = req.query;
486
+ if (!url) return res.status(400).json({ error: 'Parameter url is required' });
487
+ try {
488
+ const base64Result = await XnxxDown(url);
489
+ res.json(base64Result); // Mengirimkan buffer gambar sebagai respons
490
+ } catch (error) {
491
+ res.status(500).send(error);
492
+ }
493
+ });
494
+
495
+ /**********************************************************************************/
496
+
497
+
498
+
499
+ const SearchWhentai = async (query) => {
500
+ const headers = {
501
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
502
+ };
503
+ try {
504
+ const { data } = await axios.get("https://watchhentai.net/?s=" + query, { headers });
505
+ const $ = cheerio.load(data);
506
+ const results = [];
507
+
508
+ $('.result-item').each((_, element) => {
509
+ const title = $(element).find('.title a').text().trim();
510
+ const link = $(element).find('.title a').attr('href');
511
+ const year = $(element).find('.meta .year').text().trim();
512
+ const description = $(element).find('.contenido p').text().trim();
513
+ const image = $(element).find('.image img').attr('src');
514
+
515
+ results.push({ title, link, year, description, image });
516
+ });
517
+
518
+ return results;
519
+ } catch (error) {
520
+ throw new Error(`Failed to scrape data: ${error.message}`);
521
+ }
522
+ };
523
+
524
+
525
+ const getDataWhentai = async (url) => {
526
+ try {
527
+ const { data } = await axios.get(url, {
528
+ headers: {
529
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
530
+ },
531
+ });
532
+ const $ = cheerio.load(data);
533
+ const title = $("#single > div.content.right > div.sheader > div.data > h1").text().trim();
534
+ const thumb = $("#single > div.content.right > div.sheader > div.poster > img").attr('src');
535
+ const type = $("#single > div.content.right > div.sheader > div.poster > div > span").text().trim();
536
+ const year = $("#single > div.content.right > div.sheader > div.data > div.sgeneros > a:nth-child(1)").text().trim();
537
+ const tags = $("#single > div.content.right > div.sheader > div.data > div.sgeneros > a")
538
+ .map((_, el) => $(el).text().trim())
539
+ .get();
540
+ const sinopsis = $("#single > div.content.right > div:nth-child(2) > div > p").text().trim();
541
+ const alternativeTitle = $("#single > div.content.right > div:nth-child(5) > div:nth-child(2) > span > h3").text().trim();
542
+ const releaseDate = $("#single > div.content.right > div:nth-child(5) > div:nth-child(3) > span").text().trim();
543
+ const lastUpdated = $("#single > div.content.right > div:nth-child(5) > div:nth-child(4) > span").text().trim();
544
+ const season = $("#single > div.content.right > div:nth-child(5) > div:nth-child(5) > span").text().trim();
545
+ const totalEpisodes = $("#single > div.content.right > div:nth-child(5) > div:nth-child(6) > span").text().trim();
546
+ const averageDuration = $("#single > div.content.right > div:nth-child(5) > div:nth-child(7) > span").text().trim();
547
+ const quality = $("#single > div.content.right > div:nth-child(5) > div:nth-child(8) > span > a")
548
+ .map((_, el) => $(el).text().trim())
549
+ .get();
550
+ const studio = $("#single > div.content.right > div:nth-child(5) > div:nth-child(9) > span > a").text().trim();
551
+
552
+ const episodes = $("#seasons > div > div > ul > li").map((_, el) => {
553
+ const episodeThumb = $(el).find("div.imagen > img").attr('src');
554
+ const episodeUrl = $(el).find("div.episodiotitle > a").attr('href').replace("/videos/", "/download/");
555
+ const episodeTitle = $(el).find("div.episodiotitle > a").text().trim();
556
+ const uploadDate = $(el).find("div.episodiotitle > span").text().trim();
557
+ return { episodeUrl, episodeTitle, uploadDate };
558
+ }).get();
559
+
560
+ return {
561
+ title,
562
+ thumb,
563
+ type,
564
+ year,
565
+ tags,
566
+ sinopsis,
567
+ episodes,
568
+ };
569
+ } catch (error) {
570
+ throw new Error(`Failed to scrape data: ${error.message}`);
571
+ }
572
+ };
573
+
574
+
575
+ const DownloadWhentai = async (url) => {
576
+ try {
577
+ const { data } = await axios.get(url, {
578
+ headers: {
579
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
580
+ },
581
+ });
582
+ const $ = cheerio.load(data);
583
+ const buttons = $("#single > div.content.right > div > div._4continuar > button").map((_, el) => {
584
+ const href = $(el).attr('onclick')?.match(/window\.location\.href\s*=\s*'([^']+)'/)?.[1];
585
+ const text = $(el).text().trim();
586
+ return { href, text };
587
+ }).get();
588
+ return buttons
589
+ } catch (error) {
590
+ throw new Error(`Failed to scrape data: ${error.message}`);
591
+ }
592
+ };
593
+
594
+
595
+ app.get('/whentai/search', async (req, res) => {
596
+ const { query } = req.query;
597
+ if (!query) return res.status(400).json({ error: 'Parameter query is required' });
598
+ try {
599
+ let result = await SearchWhentai(query);
600
+ res.json(result); // Mengirimkan buffer gambar sebagai respons
601
+ } catch (error) {
602
+ res.status(500).send(error);
603
+ }
604
+ });
605
+
606
+ app.get('/whentai/detail', async (req, res) => {
607
+ const { url } = req.query;
608
+ if (!url) return res.status(400).json({ error: 'Parameter url is required' });
609
+ try {
610
+ let result = await getDataWhentai(url);
611
+ res.json(result); // Mengirimkan buffer gambar sebagai respons
612
+ } catch (error) {
613
+ res.status(500).send(error);
614
+ }
615
+ });
616
+
617
+ app.get('/whentai/download', async (req, res) => {
618
+ const { url } = req.query;
619
+ if (!url) return res.status(400).json({ error: 'Parameter url is required' });
620
+ try {
621
+ let result = await DownloadWhentai(url);
622
+ res.json({ result }); // Mengirimkan buffer gambar sebagai respons
623
+ } catch (error) {
624
+ res.status(500).send(error);
625
+ }
626
+ });
627
+
628
+
629
+
630
  /*******************************/
631
  // Fungsi untuk ping website
632
  async function pingWebsite() {