ytdl-api / index.js
Fourstore's picture
update
d00e996
import express from 'express';
import fs from 'fs';
import path from 'path';
import cors from 'cors';
import { fileURLToPath } from 'url';
import axios from 'axios';
import YTDown from './scrape/ytdown.js';
import extractAndDownloadTikTok from './scrape/tiktokdl.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const PORT = process.env.PORT || 7860;
const domain = process.env.DOMAIN || `https://fourstore-ytdl-api.hf.space`;
const LIB_FOLDER = path.join(__dirname, 'downloads');
let visitCount = 0;
let successDownloads = 0;
let failedDownloads = 0;
if (!fs.existsSync(LIB_FOLDER)) {
fs.mkdirSync(LIB_FOLDER, { recursive: true });
}
app.use(cors({
origin: '*',
methods: ['GET', 'POST', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
app.use(express.json());
app.use('/image', express.static(path.join(__dirname, 'image')));
// Middleware untuk memaksa download file MP3/MP4
app.use('/download', (req, res, next) => {
const filePath = path.join(LIB_FOLDER, req.path);
if (fs.existsSync(filePath)) {
const ext = path.extname(filePath).toLowerCase();
if (ext === '.mp3' || ext === '.mp4') {
res.setHeader('Content-Disposition', `attachment; filename="${path.basename(filePath)}"`);
res.setHeader('Content-Type', ext === '.mp3' ? 'audio/mpeg' : 'video/mp4');
return res.sendFile(filePath);
}
}
next();
}, express.static(LIB_FOLDER));
const ytdown = new YTDown();
app.get('/api/download/audio', async (req, res) => {
const { url, quality = '128K' } = req.query;
if (!url) return res.status(400).json({ error: "YouTube URL required" });
try {
const info = await ytdown.getVideoInfo(url);
if (info?.api?.status !== 'ok') {
throw new Error(info?.api?.message || 'Gagal mendapatkan info video');
}
let previewUrl = null;
let mediaInfo = null;
for (const item of info.api.mediaItems) {
if (item.type === 'Audio') {
if (item.mediaQuality === quality) {
previewUrl = item.mediaPreviewUrl;
mediaInfo = item;
break;
}
if (!previewUrl) {
previewUrl = item.mediaPreviewUrl;
mediaInfo = item;
}
}
}
if (!previewUrl) {
throw new Error('Audio tidak ditemukan');
}
const fileRes = await axios({
method: 'GET',
url: previewUrl,
responseType: 'arraybuffer',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://www.youtube.com/'
}
});
const fileId = Math.random().toString(36).substring(2, 8);
const safeTitle = info.api.title.replace(/[^a-z0-9]/gi, '_').substring(0, 50);
const filename = `${safeTitle}_${fileId}.mp3`;
const filePath = path.join(LIB_FOLDER, filename);
fs.writeFileSync(filePath, Buffer.from(fileRes.data));
successDownloads++;
res.json({
success: true,
type: 'AUDIO',
title: info.api.title,
channel: info.api.userInfo?.name,
duration: mediaInfo?.mediaDuration,
thumbnail: info.api.imagePreviewUrl,
quality: mediaInfo?.mediaQuality,
size: mediaInfo?.mediaFileSize,
download_url: `${domain}/download/${filename}`
});
} catch (error) {
failedDownloads++;
res.status(500).json({ success: false, error: error.message });
}
});
app.get('/api/download/video', async (req, res) => {
const { url, quality = 'HD' } = req.query;
if (!url) return res.status(400).json({ error: "YouTube URL required" });
try {
const info = await ytdown.getVideoInfo(url);
if (info?.api?.status !== 'ok') {
throw new Error(info?.api?.message || 'Gagal mendapatkan info video');
}
let previewUrl = null;
let mediaInfo = null;
const qualityMap = {
'1080': 'FHD', 'fhd': 'FHD',
'720': 'HD', 'hd': 'HD',
'480': 'SD', '360': 'SD', '240': 'SD', '144': 'SD'
};
const targetQuality = qualityMap[String(quality).toLowerCase()] || quality;
for (const item of info.api.mediaItems) {
if (item.type === 'Video') {
if (item.mediaQuality === targetQuality) {
previewUrl = item.mediaPreviewUrl;
mediaInfo = item;
break;
}
if (!previewUrl) {
previewUrl = item.mediaPreviewUrl;
mediaInfo = item;
}
}
}
if (!previewUrl) {
throw new Error('Video tidak ditemukan');
}
const fileRes = await axios({
method: 'GET',
url: previewUrl,
responseType: 'arraybuffer',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://www.youtube.com/'
}
});
const fileId = Math.random().toString(36).substring(2, 8);
const safeTitle = info.api.title.replace(/[^a-z0-9]/gi, '_').substring(0, 50);
const filename = `${safeTitle}_${fileId}.mp4`;
const filePath = path.join(LIB_FOLDER, filename);
fs.writeFileSync(filePath, Buffer.from(fileRes.data));
successDownloads++;
res.json({
success: true,
type: 'VIDEO',
title: info.api.title,
channel: info.api.userInfo?.name,
duration: mediaInfo?.mediaDuration,
thumbnail: info.api.imagePreviewUrl,
quality: mediaInfo?.mediaQuality,
resolution: mediaInfo?.mediaRes,
size: mediaInfo?.mediaFileSize,
download_url: `${domain}/download/${filename}`
});
} catch (error) {
failedDownloads++;
res.status(500).json({ success: false, error: error.message });
}
});
app.get('/api/download/tiktok', async (req, res) => {
try {
const { url } = req.query;
if (!url) return res.status(400).json({ error: "TikTok URL required" });
const result = await extractAndDownloadTikTok(url, domain);
successDownloads++;
res.json(result);
} catch (error) {
failedDownloads++;
res.status(500).json({ success: false, error: error.message });
}
});
app.get('/', (req, res) => {
visitCount++;
res.send(`<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FourStore Downloader | YouTube & TikTok</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
min-height: 100vh;
color: #fff;
}
.bg-animation {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
overflow: hidden;
}
.bg-animation span {
position: absolute;
width: 4px;
height: 4px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
animation: float 15s infinite linear;
}
@keyframes float {
0% { transform: translateY(100vh) scale(0); opacity: 0; }
100% { transform: translateY(-100vh) scale(1); opacity: 0.5; }
}
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
background: rgba(15, 12, 41, 0.95);
backdrop-filter: blur(20px);
padding: 16px 32px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1000;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.logo {
display: flex;
align-items: center;
gap: 12px;
}
.logo-icon {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #ff4b2b, #ff416c);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
}
.logo-text {
font-size: 20px;
font-weight: 700;
background: linear-gradient(135deg, #fff, #ffd89b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.menu-icon {
font-size: 24px;
cursor: pointer;
display: none;
}
.sidebar {
position: fixed;
top: 0;
left: -280px;
width: 280px;
height: 100%;
background: rgba(15, 12, 41, 0.98);
backdrop-filter: blur(20px);
z-index: 1100;
transition: 0.3s ease;
padding: 80px 24px 24px;
border-right: 1px solid rgba(255,255,255,0.1);
}
.sidebar.open {
left: 0;
}
.close-btn {
position: absolute;
top: 20px;
right: 20px;
font-size: 28px;
cursor: pointer;
}
.sidebar a {
display: block;
padding: 14px 16px;
color: #fff;
text-decoration: none;
border-radius: 12px;
margin-bottom: 8px;
transition: 0.3s;
}
.sidebar a:hover {
background: rgba(255,75,43,0.2);
transform: translateX(8px);
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 100px 24px 60px;
}
.hero {
text-align: center;
margin-bottom: 48px;
}
.hero h1 {
font-size: 48px;
font-weight: 800;
background: linear-gradient(135deg, #fff, #ffd89b, #ff4b2b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 16px;
}
.hero p {
font-size: 18px;
color: rgba(255,255,255,0.7);
}
.card {
background: rgba(255,255,255,0.05);
backdrop-filter: blur(10px);
border-radius: 28px;
padding: 32px;
border: 1px solid rgba(255,255,255,0.1);
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
}
.input-wrapper {
margin-bottom: 24px;
}
.input-wrapper input {
width: 100%;
padding: 18px 20px;
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.2);
border-radius: 20px;
font-size: 16px;
color: #fff;
outline: none;
transition: 0.3s;
}
.input-wrapper input:focus {
border-color: #ff4b2b;
background: rgba(255,255,255,0.15);
}
.input-wrapper input::placeholder {
color: rgba(255,255,255,0.5);
}
.btn-group {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 32px;
}
.btn {
padding: 14px;
border: none;
border-radius: 16px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: 0.3s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-youtube-audio {
background: linear-gradient(135deg, #ff4b2b, #ff416c);
color: white;
}
.btn-youtube-video {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
}
.btn-tiktok {
background: linear-gradient(135deg, #25F4EE, #000000);
color: white;
}
.btn:hover {
transform: translateY(-3px);
filter: brightness(1.1);
}
.loading {
display: none;
text-align: center;
margin: 24px 0;
}
.spinner {
width: 48px;
height: 48px;
border: 3px solid rgba(255,255,255,0.2);
border-top: 3px solid #ff4b2b;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 12px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.result {
margin-top: 32px;
}
.result-card {
background: rgba(255,255,255,0.1);
border-radius: 20px;
padding: 24px;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.result-card h3 {
font-size: 18px;
margin-bottom: 12px;
}
.result-card img {
width: 100%;
max-height: 200px;
object-fit: cover;
border-radius: 16px;
margin: 12px 0;
}
.result-info {
margin: 12px 0;
font-size: 14px;
color: rgba(255,255,255,0.8);
}
.result-info p {
margin: 6px 0;
}
.download-btn {
display: inline-block;
width: 100%;
padding: 14px;
background: linear-gradient(135deg, #00d4ff, #667eea);
border: none;
border-radius: 16px;
color: white;
font-weight: 700;
text-align: center;
text-decoration: none;
cursor: pointer;
transition: 0.3s;
margin-top: 16px;
}
.download-btn:hover {
transform: scale(1.02);
filter: brightness(1.1);
}
.stats {
display: flex;
gap: 16px;
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid rgba(255,255,255,0.1);
}
.stat-box {
flex: 1;
text-align: center;
background: rgba(255,255,255,0.05);
padding: 16px;
border-radius: 20px;
}
.stat-box span {
font-size: 28px;
font-weight: 800;
display: block;
margin-bottom: 6px;
background: linear-gradient(135deg, #ffd89b, #ff4b2b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-box p {
font-size: 12px;
color: rgba(255,255,255,0.6);
}
footer {
text-align: center;
padding: 32px;
color: rgba(255,255,255,0.5);
font-size: 13px;
}
footer a {
color: #ff4b2b;
text-decoration: none;
}
@media (max-width: 600px) {
.container { padding: 80px 16px 40px; }
.hero h1 { font-size: 32px; }
.btn-group { grid-template-columns: 1fr; }
.menu-icon { display: block; }
.stats { flex-direction: column; }
.card { padding: 24px; }
}
</style>
</head>
<body>
<div class="bg-animation" id="bgAnimation"></div>
<div class="navbar">
<div class="logo">
<div class="logo-icon">🎬</div>
<div class="logo-text">FourStore Downloader</div>
</div>
<div class="menu-icon" onclick="toggleSidebar()">☰</div>
</div>
<div class="sidebar" id="sidebar">
<div class="close-btn" onclick="toggleSidebar()">Γ—</div>
<a href="/">🏠 Beranda</a>
<a href="/docs">πŸ“š Dokumentasi</a>
<a href="https://wa.me/6283163784116" target="_blank">πŸ’¬ WhatsApp</a>
<a href="https://github.com/" target="_blank">πŸ™ GitHub</a>
</div>
<div class="container">
<div class="hero">
<h1>Download Anything</h1>
<p>YouTube Audio, Video & TikTok β€” Fast & Free</p>
</div>
<div class="card">
<div class="input-wrapper">
<input type="text" id="url" placeholder="Paste URL YouTube or TikTok...">
</div>
<div class="btn-group">
<button class="btn btn-youtube-audio" onclick="unduh('audio')">🎡 YouTube Audio</button>
<button class="btn btn-youtube-video" onclick="unduh('video')">🎬 YouTube Video</button>
<button class="btn btn-tiktok" onclick="unduh('tiktok')">πŸ“± TikTok</button>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Processing your request...</p>
</div>
<div id="hasil" class="result"></div>
<div class="stats">
<div class="stat-box">
<span id="jumlahKunjungan">${visitCount}</span>
<p>Total Visits</p>
</div>
<div class="stat-box">
<span id="berhasilDiunduh">${successDownloads}</span>
<p>Successful</p>
</div>
<div class="stat-box">
<span id="gagalDiunduh">${failedDownloads}</span>
<p>Failed</p>
</div>
</div>
</div>
</div>
<footer>
<p>Made with ❀️ by <strong>Fourstore</strong> | Owner: RezaHaris</p>
<p>πŸ’¬ <a href="https://wa.me/6283163784116" target="_blank">Contact Support</a></p>
</footer>
<script>
const domain = window.location.origin;
for (let i = 0; i < 100; i++) {
const span = document.createElement('span');
span.style.left = Math.random() * 100 + '%';
span.style.animationDelay = Math.random() * 15 + 's';
span.style.animationDuration = 10 + Math.random() * 20 + 's';
document.getElementById('bgAnimation').appendChild(span);
}
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('open');
}
async function unduh(jenis) {
const url = document.getElementById('url').value.trim();
if (!url) {
alert("⚠️ Harap masukkan URL!");
return;
}
document.getElementById('loading').style.display = 'block';
document.getElementById('hasil').innerHTML = '';
try {
let endpoint = jenis === 'tiktok' ? 'tiktok' : (jenis === 'audio' ? 'audio' : 'video');
const response = await fetch(domain + '/api/download/' + endpoint + '?url=' + encodeURIComponent(url));
const hasil = await response.json();
document.getElementById('loading').style.display = 'none';
if (hasil.success) {
if (jenis === 'tiktok') {
let html = '<div class="result-card">';
if (hasil.type === 'video') {
html += '<h3>🎬 TikTok Video</h3>';
html += '<p>' + (hasil.description || 'No description') + '</p>';
html += '<video controls style="width:100%; border-radius:16px; margin:12px 0;"><source src="' + hasil.downloadInfo?.download_url + '" type="video/mp4"></video>';
html += '<a href="' + hasil.downloadInfo?.download_url + '" class="download-btn" download>⬇️ Download Video</a>';
} else if (hasil.images) {
html += '<h3>πŸ–ΌοΈ TikTok Slides</h3><div style="display:grid; grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); gap:12px; margin:16px 0;">';
for (let img of hasil.images) {
html += '<div><img src="' + img.download_url + '" style="width:100%; border-radius:12px;"><a href="' + img.download_url + '" download style="display:block;text-align:center;margin-top:8px;font-size:12px;">⬇️ Save</a></div>';
}
html += '</div><a href="' + hasil.folder_url + '" class="download-btn">πŸ“¦ Download All Images</a>';
}
if (hasil.musicInfo?.download_url) {
html += '<a href="' + hasil.musicInfo.download_url + '" class="download-btn" style="margin-top:12px;">🎡 Download Audio</a>';
}
html += '</div>';
document.getElementById('hasil').innerHTML = html;
} else {
let html = '<div class="result-card">';
html += '<h3>✨ ' + hasil.title + '</h3>';
if (hasil.thumbnail) html += '<img src="' + hasil.thumbnail + '" alt="Thumbnail">';
html += '<div class="result-info">';
html += '<p>πŸ“Ί Channel: ' + (hasil.channel || '-') + '</p>';
html += '<p>⏱️ Duration: ' + (hasil.duration || '-') + '</p>';
html += '<p>🎚️ Quality: ' + (hasil.quality || '-') + '</p>';
html += '<p>πŸ“¦ Size: ' + (hasil.size || '-') + '</p>';
html += '</div>';
html += '<a href="' + hasil.download_url + '" class="download-btn" download>⬇️ Download ' + hasil.type + '</a>';
html += '</div>';
document.getElementById('hasil').innerHTML = html;
}
updateStats(true);
} else {
document.getElementById('hasil').innerHTML = '<div class="result-card" style="background:rgba(255,75,43,0.2);"><p>❌ ' + (hasil.error || 'Download failed') + '</p></div>';
updateStats(false);
}
} catch (error) {
document.getElementById('loading').style.display = 'none';
document.getElementById('hasil').innerHTML = '<div class="result-card" style="background:rgba(255,75,43,0.2);"><p>❌ Error: ' + error.message + '</p></div>';
updateStats(false);
}
}
function updateStats(success) {
let visits = parseInt(document.getElementById('jumlahKunjungan').textContent);
document.getElementById('jumlahKunjungan').textContent = visits + 1;
if (success) {
let successCount = parseInt(document.getElementById('berhasilDiunduh').textContent);
document.getElementById('berhasilDiunduh').textContent = successCount + 1;
} else {
let failCount = parseInt(document.getElementById('gagalDiunduh').textContent);
document.getElementById('gagalDiunduh').textContent = failCount + 1;
}
}
</script>
</body>
</html>
`);
});
app.get('/docs', (req, res) => {
res.send(`<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Documentation - FourStore Downloader</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
color: #fff;
min-height: 100vh;
}
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
background: rgba(15,12,41,0.95);
backdrop-filter: blur(20px);
padding: 16px 32px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1000;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.logo {
display: flex;
align-items: center;
gap: 12px;
}
.logo-icon {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #ff4b2b, #ff416c);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
}
.logo-text {
font-size: 20px;
font-weight: 700;
background: linear-gradient(135deg, #fff, #ffd89b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.menu-icon { font-size: 24px; cursor: pointer; display: none; }
.sidebar {
position: fixed;
top: 0;
left: -280px;
width: 280px;
height: 100%;
background: rgba(15,12,41,0.98);
backdrop-filter: blur(20px);
z-index: 1100;
transition: 0.3s ease;
padding: 80px 24px 24px;
border-right: 1px solid rgba(255,255,255,0.1);
}
.sidebar.open { left: 0; }
.close-btn { position: absolute; top: 20px; right: 20px; font-size: 28px; cursor: pointer; }
.sidebar a {
display: block;
padding: 14px 16px;
color: #fff;
text-decoration: none;
border-radius: 12px;
margin-bottom: 8px;
transition: 0.3s;
}
.sidebar a:hover { background: rgba(255,75,43,0.2); transform: translateX(8px); }
.container { max-width: 900px; margin: 0 auto; padding: 100px 24px 60px; }
.card {
background: rgba(255,255,255,0.05);
backdrop-filter: blur(10px);
border-radius: 28px;
padding: 40px;
border: 1px solid rgba(255,255,255,0.1);
}
h1 { font-size: 36px; margin-bottom: 8px; background: linear-gradient(135deg, #fff, #ffd89b); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.endpoint {
background: rgba(0,0,0,0.3);
border-radius: 20px;
padding: 24px;
margin: 24px 0;
border-left: 4px solid #ff4b2b;
}
.method {
display: inline-block;
padding: 4px 12px;
background: #ff4b2b;
border-radius: 20px;
font-size: 12px;
font-weight: bold;
margin-right: 12px;
}
.url { font-family: monospace; font-size: 14px; color: #ffd89b; }
pre {
background: rgba(0,0,0,0.5);
padding: 16px;
border-radius: 12px;
overflow-x: auto;
margin: 16px 0;
font-size: 13px;
}
table { width: 100%; border-collapse: collapse; margin: 16px 0; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid rgba(255,255,255,0.1); }
th { color: #ffd89b; }
footer { text-align: center; padding: 32px; color: rgba(255,255,255,0.5); font-size: 13px; }
a { color: #ff4b2b; text-decoration: none; }
@media (max-width: 600px) { .menu-icon { display: block; } .container { padding: 80px 16px 40px; } .card { padding: 24px; } }
</style>
</head>
<body>
<div class="navbar">
<div class="logo"><div class="logo-icon">πŸ“š</div><div class="logo-text">API Docs</div></div>
<div class="menu-icon" onclick="toggleSidebar()">☰</div>
</div>
<div class="sidebar" id="sidebar">
<div class="close-btn" onclick="toggleSidebar()">Γ—</div>
<a href="/">🏠 Home</a>
<a href="/docs">πŸ“š Documentation</a>
<a href="https://wa.me/6283163784116" target="_blank">πŸ’¬ WhatsApp</a>
</div>
<div class="container">
<div class="card">
<h1>πŸ“– API Documentation</h1>
<p>REST API for downloading YouTube and TikTok content</p>
<div class="endpoint">
<h3><span class="method">GET</span> <span class="url">/api/download/audio</span></h3>
<p>Download YouTube audio as MP3</p>
<h4>Parameters:</h4>
<table>
<tr><th>Parameter</th><th>Type</th><th>Required</th><th>Description</th></tr>
<tr><td>url</d><d>string</d><d>βœ“</d><d>YouTube video URL</d></tr>
<tr><td>quality</d><d>string</d><d>βœ—</d><d>48K or 128K (default: 128K)</d></tr>
</table>
<h4>Example:</h4>
<pre>GET ${domain}/api/download/audio?url=https://youtube.com/watch?v=VIDEO_ID</pre>
</div>
<div class="endpoint">
<h3><span class="method">GET</span> <span class="url">/api/download/video</span></h3>
<p>Download YouTube video as MP4</p>
<h4>Parameters:</h4>
<table>
<tr><th>Parameter</th><th>Type</th><th>Required</th><th>Description</th></tr>
<tr><td>url</d><d>string</d><d>βœ“</d><d>YouTube video URL</d></tr>
<tr><td>quality</d><d>string</d><d>βœ—</d><d>144/240/360/480/720/1080 or SD/HD/FHD (default: HD)</d></tr>
</table>
<h4>Example:</h4>
<pre>GET ${domain}/api/download/video?url=https://youtube.com/watch?v=VIDEO_ID&quality=720</pre>
</div>
<div class="endpoint">
<h3><span class="method">GET</span> <span class="url">/api/download/tiktok</span></h3>
<p>Download TikTok video or slides</p>
<h4>Parameters:</h4>
<table>
<tr><th>Parameter</th><th>Type</th><th>Required</th><th>Description</th></tr>
<tr><td>url</d><d>string</d><d>βœ“</d><d>TikTok video/photo URL</d></tr>
</table>
<h4>Example:</h4>
<pre>GET ${domain}/api/download/tiktok?url=https://tiktok.com/@user/video/123456789</pre>
</div>
<h3>πŸ“¦ Response Format</h3>
<pre>{
"success": true,
"type": "AUDIO",
"title": "Video Title",
"channel": "Channel Name",
"duration": "03:15",
"thumbnail": "https://...",
"quality": "128K",
"size": "3.31 MB",
"download_url": "${domain}/download/audio_abc123.mp3"
}</pre>
</div>
</div>
<footer>Made with ❀️ by Fourstore | Owner: RezaHaris | <a href="https://wa.me/6283163784116">Contact</a></footer>
<script>
function toggleSidebar() { document.getElementById('sidebar').classList.toggle('open'); }
</script>
</body>
</html>
`);
});
app.listen(PORT, () => {
console.log(`Server running at ${domain}`);
console.log(`Documentation available at ${domain}/docs`);
});