| | 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'); |
| |
|
| | |
| | const ytIdRegex = /(?:http(?:s|):\/\/|)(?:(?:www\.|)?youtube(?:\-nocookie|)\.com\/(?:shorts\/)?(?:watch\?.*(?:|\&)v=|embed\/|v\/)?|youtu\.be\/)([-_0-9A-Za-z]{11})/; |
| |
|
| | |
| | const post = async (url, form, headers = {}) => { |
| | const response = await fetch(url, { |
| | method: 'post', |
| | body: new URLSearchParams(form), |
| | headers |
| | }); |
| | return response; |
| | }; |
| |
|
| | |
| | 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; |
| | } |
| | } |
| |
|
| | |
| | const convert = async (url, v_id, ftype, fquality, fname, token, timeExpire) => { |
| | let params = { |
| | v_id, |
| | ftype, |
| | fquality, |
| | fname, |
| | token, |
| | timeExpire, |
| | client: 'yt5s.com' |
| | }; |
| |
|
| | |
| | let resServer = await (await post(url, params, { 'x-requested-key': 'de0cfuirtgf67a' })).json(); |
| | let server = resServer.c_server; |
| |
|
| | |
| | if (!server && ftype === 'mp3') return server || resServer.d_url || ''; |
| |
|
| | |
| | let data = await (await post(`${server}/api/json/convert`, params)).json(); |
| | let result; |
| |
|
| | |
| | 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; |
| | }; |
| |
|
| | |
| | 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) |
| | }) |
| | |
| | } |
| | 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 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 buffermp3 = await streamToBuffer(audioStream) |
| | buffermp3 = await convertMp4ToAudio(buffermp3) |
| | |
| | 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 (!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 response = { |
| | ...dataInfo, |
| | mp4: data.mp4_url, |
| | mp3: data.mp3_url, |
| | |
| | }; |
| |
|
| | 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')); |