const yts = require('yt-search'); const morgan = require('morgan'); const express = require('express'); const ytdl = require('ytdl-core'); const { Writable, pipeline } = require('stream'); const ffmpeg = require("fluent-ffmpeg") const util = require('util'); const axios = require('axios'); // Regex untuk mengidentifikasi URL YouTube dan Mega const ytIdRegex = /(?:http(?:s|):\/\/|)(?:(?:www\.|)?youtube(?:\-nocookie|)\.com\/(?:shorts\/)?(?:watch\?.*(?:|\&)v=|embed\/|v\/)?|youtu\.be\/)([-_0-9A-Za-z]{11})/; // Fungsi untuk melakukan HTTP POST request const post = async (url, form, headers = {}) => { const response = await fetch(url, { method: 'post', body: new URLSearchParams(form), headers }); return response; }; //YTDL-CORE async function uploadBuffer(buffer) { return new Promise(async (resolve, reject) => { let res = await axios.post('https://ilhamdev-up.hf.space/upload', { file: buffer.toString('base64'), headers: { 'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0" } }).catch(e => reject(e)) if (res.status !== 200) { resolve(res?.statusText) } else { resolve(res?.data?.url) } }) } async function streamToBuffer(stream) { const chunks = []; const captureChunks = new Writable({ write(chunk, encoding, callback) { chunks.push(chunk); callback(); } }); await util.promisify(pipeline)(stream, captureChunks); return Buffer.concat(chunks); } function formatViews(viewCount) { if (viewCount >= 1000000000) { return (viewCount / 1000000000).toFixed(1) + 'B'; } else if (viewCount >= 1000000) { return (viewCount / 1000000).toFixed(1) + 'M'; } else { return viewCount >= 1000 ? (viewCount / 1000).toFixed(1) + 'K' : viewCount.toString(); } } function formatDuration(durationInSeconds) { const hours = Math.floor(durationInSeconds / 3600); const minutes = Math.floor((durationInSeconds % 3600) / 60); const seconds = durationInSeconds % 60; return hours > 0 ? hours + ':' + minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0') : minutes + ':' + seconds.toString().padStart(2, '0'); } function formatSize(bytes, si = false, dp = 2) { const thresh = si ? 1000 : 1024; if (Math.abs(bytes) < thresh) { return `${bytes} B`; } const units = si ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; let u = -1; const r = 10 ** dp; do { bytes /= thresh; ++u; } while ( Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1 ); return `${bytes.toFixed(dp)} ${units[u]}`; } async function ytmp4(url, quality = 'highestvideo') { try { const ID = ytdl.getVideoID(url), data = await ytdl.getInfo('https://www.youtube.com/watch?v=' + ID) const videoStream = await ytdl(ID, { filter: 'videoandaudio', quality: 'highestvideo' }); let buffer = await streamToBuffer(videoStream) let upload = await uploadBuffer(buffer) let format = ytdl.chooseFormat(data.formats, { filter: 'videoandaudio', quality: quality }); if (format) { return { title: data.videoDetails.title, description: data.videoDetails.description, channel: data.videoDetails.ownerChannelName, views: formatViews(data.videoDetails.viewCount), publish: data.videoDetails.publishDate, duration: formatDuration(data.videoDetails.lengthSeconds), size: format.contentLength ? formatSize(format.contentLength) : 0, quality: format.qualityLabel, thumb: data.videoDetails.thumbnails[0].url, dl_url: format.url, url_v2: upload, } } else { throw new Error('No suitable format found'); } } catch (error) { console.error('Error occurred:', error); return null; } } async function ytmp3(url, bitrate = 'lowestaudio') { try { const ID = ytdl.getVideoID(url), data = await ytdl.getInfo('https://www.youtube.com/watch?v=' + ID) let audioStream = await ytdl(ID, { filter: 'audioonly', quality: 'lowestaudio', }); let buffer = await streamToBuffer(audioStream) let upload = await uploadBuffer(buffer) let format = ytdl.chooseFormat(data.formats, { filter: 'audioonly', quality: bitrate }); if (format) { return { title: data.videoDetails.title, description: data.videoDetails.description, channel: data.videoDetails.ownerChannelName, views: formatViews(data.videoDetails.viewCount), publish: data.videoDetails.publishDate, duration: formatDuration(data.videoDetails.lengthSeconds), size: format.contentLength ? formatSize(format.contentLength) : 0, quality: format.audioQuality, thumb: data.videoDetails.thumbnails[0].url, dl_url: format.url, url_v2: upload } } else { throw new Error('No suitable format found'); } } catch (error) { console.error('Error occurred:', error); return null; } } // Fungsi untuk mengkonversi video dari YouTube const convert = async (url, v_id, ftype, fquality, fname, token, timeExpire) => { let params = { v_id, ftype, fquality, fname, token, timeExpire, client: 'yt5s.com' }; // Mengirim permintaan konversi let resServer = await (await post(url, params, { 'x-requested-key': 'de0cfuirtgf67a' })).json(); let server = resServer.c_server; // Jika tidak ada server dan tipe file adalah mp3, kembalikan null if (!server && ftype === 'mp3') return server || resServer.d_url || ''; // Mengambil data hasil konversi let data = await (await post(`${server}/api/json/convert`, params)).json(); let result; // Memeriksa status kode hasil konversi if (data.statusCode === 200) result = data.result; while (!result) { let json = await (await post(`${server}/api/json/convert`, params)).json(); if (json.statusCode === 200) { result = json.result; break; } await new Promise(resolve => setTimeout(resolve, 2000)); } return result; }; // Fungsi untuk mendownload video dari YouTube const youtubedl = async (url) => { let html = await (await fetch('https://yt5s.com/en32')).text(); let urlAjax = (html.match(/k_url_search="(.*?)"/) || [])[1]; let urlConvert = (html.match(/k_url_convert="(.*?)"/) || [])[1]; let json = await (await post(urlAjax, { q: url, vt: 'home' })).json(); let video = {}, audio = {}; if (!json?.links) throw json.mess; Object.values(json.links.mp4).map(({ k, size }) => video[k] = { quality: k, fileSizeH: size, fileSize: parseFloat(size) * (/MB$/.test(size) ? 1000 : 1), download: convert.bind(null, urlConvert, json.vid, 'mp4', k, json.fn, json.token, parseInt(json.timeExpires)) }); Object.values(json.links.mp3).map(({ key, size }) => audio[key] = { quality: key, fileSizeH: size, fileSize: parseFloat(size) * (/MB$/.test(size) ? 1000 : 1), download: convert.bind(null, urlConvert, json.vid, 'mp3', key.replace(/kbps/i, ''), json.fn, json.token, parseInt(json.timeExpires)) }); return { id: json.vid, title: json.title, thumbnail: `https://i.ytimg.com/vi/${json.vid}/0.jpg`, video, audio }; }; async function streamToBuffer(stream) { const chunks = []; const captureChunks = new Writable({ write(chunk, encoding, callback) { chunks.push(chunk); callback(); } }); await util.promisify(pipeline)(stream, captureChunks); return Buffer.concat(chunks); } async function fileDitch(media){ return new Promise(async (resolve, reject) => { let {fileTypeFromBuffer} = await (await import('file-type')) let mime = await fileTypeFromBuffer(media) let form = new FormData() form.append("files[]", media, `file-${new Date().getTime()}.${mime.ext}`) axios.post("https://up1.fileditch.com/temp/upload.php", form, { headers: { "User-Agent": generateRandomUserAgent(), "X-Forwarded-For": generateRandomIP(), ...form.getHeaders() } }).then(({ data }) => resolve(data?.files[0]?.url)).catch(reject) }) //https://up1.fileditch.com/upload.php } async function convertMp4ToAudio(inputBuffer) { return new Promise((resolve, reject) => { const inputStream = new Readable(); inputStream.push(inputBuffer); inputStream.push(null); const outputBuffer = []; const outputStream = new Writable({ write(chunk, encoding, callback) { outputBuffer.push(chunk); callback(); } }); ffmpeg(inputStream) .toFormat('mp3') .on('end', () => { console.log('Conversion finished!'); resolve(Buffer.concat(outputBuffer)); }) .on('error', (err) => { console.error('Error during conversion:', err); reject(err); }) .pipe(outputStream); }); } async function ytAPI(url) { try { const ID = ytdl.getVideoID(url) //let videoStream = await ytdl(ID, { filter: 'audioandvideo', quality: 'highestvideo' }); let data = await ytdl.getInfo('https://www.youtube.com/watch?v=' + ID) let format = ytdl.chooseFormat(data.formats, { filter: 'videoandaudio', quality: 'highestvideo' }); let audioStream = await ytdl(ID, {filter: "audioandvideo", quality:"lowestvideo"}) //let buffermp4 = await streamToBuffer(videoStream) let buffermp3 = await streamToBuffer(audioStream) buffermp3 = await convertMp4ToAudio(buffermp3) //buffermp4 = await fileDitch(buffermp4) buffermp3 = await fileDitch(buffermp3) return { mp4_url: format.url, mp3_url: buffermp3, } } catch (err) { console.error('Error occurred:', err); return null; } } const app = express() .set('json spaces', 4) .use(morgan('dev')) .use(express.json()) .all('/', (_, res) => res.send('Hello World')) .get('/yt', async (req, res) => { const host = 'https://' + req.get('host'); try { let { url, type, quality, json } = req.query; if (!ytIdRegex.test(url)) return res.json({ message: 'Invalid URL' }); if (!!json) { let ytId = ytIdRegex.exec(url)?.[1]; if (!ytId) return res.json({ message: 'No video id found' }); let data = await yts({ videoId: ytId }); const downloadUrls = { audio: `${host}/yt?url=${url}&type=audio&quality=128kbps`, video: `${host}/yt?url=${url}&type=video&quality=`, }; return res.json({ ...data, download: downloadUrls }); } if (!type || !/audio|video/i.test(type)) type = 'video'; let data = await youtubedl(url).catch(e => console.log(e)); if (!data) return res.json({ message: 'Error: link download not found' }); type = type.toLowerCase(); let result = quality ? Object.values(data[type]).find(x => x.quality == quality) : Object.values(data[type])[0]; if (quality && !result) return res.json({ message: `Invalid quality: ${quality}, available quality (${Object.keys(data[type]).join('/')})` }); if (!result) return res.json({ message: 'Error: can\'t download' }); res.redirect(await result.download()); } catch (e) { console.log(e); res.json({ message: e }); } }) .get('/search', async (req, res) => { try { let q = req.query.q || req.query.query; if (!q) return res.json({ message: 'Input parameter q' }); let data = await yts(q); if (!data.all[0]) return res.json({ message: 'Not found' }); res.json(data.all); } catch (e) { console.log(e); res.json({ message: e }); } }) .get('/ytdl', async (req, res) => { try { let { url } = req.query; //if (!ytIdRegex.test(url)) return res.json({ message: 'Invalid URL' }); if (!ytdl.validateURL(url)) return res.json({ message: 'Invalid URL' }); let data = await ytAPI(url) || {}; let videoID = ytdl.getVideoID(url); let dataInfo = await yts({ videoId: videoID }) || {}; //let otherAPI = await (await axios.get("https://line.1010diy.com/web/free-mp3-finder/detail?url="+url))?.data?.data ? await (await axios.get("https://line.1010diy.com/web/free-mp3-finder/detail?url="+url))?.data?.data : null let response = { ...dataInfo, mp4: data.mp4_url, mp3: data.mp3_url, //other: otherAPI || null }; return res.json(response); } catch (e) { console.log(e); return res.status(500).json({ message: e.message }); } }) .listen(7860, () => console.log('App running on port 7860'));