import express from 'express'; import cors from 'cors'; import dotenv from 'dotenv'; import { exec } from 'child_process'; import { promisify } from 'util'; import fs from 'fs'; import path from 'path'; import { AIService } from '@/backend/services/ai'; import { AuthService } from '@/backend/services/authService'; import { CreditService } from '@/backend/services/creditService'; import { AdminService } from '@/backend/services/adminService'; const execPromise = promisify(exec); dotenv.config(); const app = express(); // CRITICAL FIX: Increased limits to 100mb to allow processing of long audio/video base64 data app.use(express.json({ limit: '100mb' })); app.use(express.urlencoded({ limit: '100mb', extended: true })); app.use(cors()); const apiRouter = express.Router(); apiRouter.post('/download', async (req, res) => { const { url, quality, platform, sessionId, creditCost } = req.body; try { let eligibility = null; // BYPASS LOGIC: Only check eligibility and deduct credits if NOT tiktok if (platform !== 'tiktok') { eligibility = await CreditService.checkEligibility(sessionId, null, 'downloader', false, creditCost); } else { console.log(`[DOWNLOADER] TikTok bypass triggered for URL: ${url}`); } const timestamp = Date.now(); const fileBaseName = `tm_dl_${timestamp}`; const outputDir = '/tmp'; const extension = quality === 'audio' ? 'mp3' : 'mp4'; const outputPath = path.join(outputDir, `${fileBaseName}.${extension}`); let formatStr = ""; if (quality === 'audio') { formatStr = "-f 'ba' -x --audio-format mp3"; } else { const qMap = { '360p': 'bestvideo[height<=360]+bestaudio/best[height<=360]', '720p': 'bestvideo[height<=720]+bestaudio/best[height<=720]', '1080p': 'bestvideo[height<=1080]+bestaudio/best[height<=1080]' }; const format = qMap[quality] || qMap['720p']; formatStr = `-f "${format}" --merge-output-format mp4`; } const tiktokArgs = platform === 'tiktok' ? '--referer "https://www.tiktok.com/"' : ''; const command = `yt-dlp --no-playlist --no-warnings --no-check-certificate --js-runtime node ${formatStr} ${tiktokArgs} -o "${outputPath}" "${url.trim()}"`; console.log(`[DOWNLOADER] Executing: ${command}`); try { await execPromise(command); } catch (execErr) { console.error("[DL-EXEC-FAIL]", execErr.stderr || execErr.message); if (!fs.existsSync(outputPath)) { throw new Error(`Engine Error: ${execErr.stderr || execErr.message}`); } } if (!fs.existsSync(outputPath)) { throw new Error("Download engine failed. File not generated."); } // Only commit deduction if eligibility was checked (non-tiktok) if (eligibility) { await CreditService.commitDeduction(eligibility); } res.download(outputPath, `${fileBaseName}.${extension}`, (err) => { if (err) console.error("Stream Error:", err); try { if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath); } catch (e) { console.error("Cleanup Error:", e); } }); } catch (error) { console.error("[DOWNLOAD-ERROR]", error.message); res.status(500).json({ error: error.message }); } }); const handleAiRequest = async (req, res, toolKey, taskFn) => { const sessionId = req.headers['x-session-id']; const guestId = req.headers['x-guest-id']; const { isOwnApi, customApiKey, creditCost } = req.body; try { const eligibility = await CreditService.checkEligibility(sessionId, guestId, toolKey, isOwnApi, creditCost); const apiKey = isOwnApi ? (customApiKey || process.env.API_KEY) : process.env.API_KEY; const result = await taskFn(apiKey, isOwnApi); await CreditService.commitDeduction(eligibility); res.json(result); } catch (error) { console.error(`[BACKEND-API] Error in ${toolKey}:`, error.message); res.status(error.message.includes('REACHED') || error.message.includes('INSUFFICIENT') ? 403 : 500).json({ error: error.message }); } }; apiRouter.post('/transcribe', (req, res) => { handleAiRequest(req, res, 'count_transcript', async (key, isOwn) => { return { text: await AIService.transcribe(req.body.media, req.body.mimeType, key, isOwn) }; }); }); apiRouter.post('/recap', (req, res) => { handleAiRequest(req, res, 'count_transcript', async (key, isOwn) => { return { recap: await AIService.recap(req.body.media, req.body.mimeType, req.body.targetLanguage, key, isOwn) }; }); }); apiRouter.post('/book-recap', (req, res) => { handleAiRequest(req, res, 'count_translate', async (key, isOwn) => { return { recap: await AIService.bookRecap(req.body.media, req.body.mimeType, req.body.targetLanguage, key, isOwn) }; }); }); apiRouter.post('/comic-translate', (req, res) => { handleAiRequest(req, res, 'count_translate', async (key, isOwn) => { return { pdfData: await AIService.comicTranslate(req.body.media, req.body.mimeType, req.body.targetLanguage, key, isOwn) }; }); }); apiRouter.post('/translate', (req, res) => { handleAiRequest(req, res, 'count_translate', async (key, isOwn) => { return await AIService.translate(req.body.text, req.body.targetLanguage, req.body.options, key, isOwn); }); }); apiRouter.post('/srt-translate', (req, res) => { handleAiRequest(req, res, 'count_srt_translate', async (key, isOwn) => { return { srt: await AIService.srtTranslate(req.body.srtContent, req.body.sourceLanguage, req.body.targetLanguage, key, isOwn) }; }); }); apiRouter.post('/tts', (req, res) => { handleAiRequest(req, res, 'count_tts', async (key, isOwn) => { return { audioData: await AIService.tts(req.body.text, req.body.voiceId, req.body.tone, key, isOwn) }; }); }); apiRouter.post('/subtitle', (req, res) => { req.setTimeout(1200000); handleAiRequest(req, res, 'count_subtitle', async (key, isOwn) => { return await AIService.subtitle( req.body.media, req.body.mimeType, req.body.script, key, isOwn, req.body.sourceLanguage, req.body.startOffsetMs || 0, req.body.lastScriptIndex || 0 ); }); }); apiRouter.post('/content-creator', (req, res) => { handleAiRequest(req, res, 'count_creator_text', async (key, isOwn) => { const script = await AIService.contentCreator( req.body.topic, req.body.category, req.body.subTopics, req.body.contentType, req.body.creatorGender, req.body.targetLanguage, key, isOwn ); let imageUrl = null; if (req.body.withImage) { try { imageUrl = await AIService.generateImage(req.body.topic, key, isOwn); } catch(e) {} } return { script, imageUrl }; }); }); apiRouter.post('/secure/save-key', async (req, res) => { try { const { sessionId, apiKey } = req.body; res.json(await AuthService.saveCustomKey(sessionId, apiKey)); } catch (e) { res.status(500).json({ error: e.message }); } }); apiRouter.post('/secure/update-session', async (req, res) => { try { const { userId, newSessionId } = req.body; res.json(await AuthService.updateActiveSession(userId, newSessionId)); } catch (e) { res.status(500).json({ error: e.message }); } }); apiRouter.post('/admin/verify', async (req, res) => { const { adminPassword } = req.body; if (adminPassword === process.env.ADMIN_PASSWORD) res.json({ success: true }); else res.status(403).json({ error: "Unauthorized" }); }); apiRouter.post('/admin/update-settings', async (req, res) => { const { adminPassword, settings } = req.body; if (adminPassword !== process.env.ADMIN_PASSWORD) return res.status(403).json({ error: "Denied" }); try { res.json(await AdminService.updateSettings(settings)); } catch (e) { res.status(500).json({ error: e.message }); } }); apiRouter.post('/admin/add-user', async (req, res) => { const { adminPassword, userData, referrerCode } = req.body; if (adminPassword !== process.env.ADMIN_PASSWORD) return res.status(403).json({ error: "Denied" }); try { res.json(await AdminService.addUser(userData, referrerCode)); } catch (e) { res.status(500).json({ error: e.message }); } }); apiRouter.post('/admin/reactivate-user', async (req, res) => { const { adminPassword, nodeKey, userClass, credits } = req.body; if (adminPassword !== process.env.ADMIN_PASSWORD) return res.status(403).json({ error: "Denied" }); try { res.json(await AdminService.reactivateUser(nodeKey, userClass, credits)); } catch (e) { res.status(500).json({ error: e.message }); } }); apiRouter.post('/admin/update-user-class', async (req, res) => { const { adminPassword, nodeKey, userClass, credits } = req.body; if (adminPassword !== process.env.ADMIN_PASSWORD) return res.status(403).json({ error: "Denied" }); try { res.json(await AdminService.updateUserClass(nodeKey, userClass, credits)); } catch (e) { res.status(500).json({ error: e.message }); } }); apiRouter.post('/admin/topup-credits', async (req, res) => { const { adminPassword, sessionId, amount } = req.body; if (adminPassword !== process.env.ADMIN_PASSWORD) return res.status(403).json({ error: "Denied" }); try { res.json(await AdminService.topUpCredits(sessionId, amount)); } catch (e) { res.status(500).json({ error: e.message }); } }); apiRouter.post('/admin/delete-user', async (req, res) => { const { adminPassword, nodeKey } = req.body; if (adminPassword !== process.env.ADMIN_PASSWORD) return res.status(403).json({ error: "Denied" }); try { res.json(await AdminService.deleteUser(nodeKey)); } catch (e) { res.status(500).json({ error: e.message }); } }); app.use('/', apiRouter); const PORT = process.env.PORT || 7860; app.listen(PORT, () => { console.log(`🚀 Master Backend Engine running on port ${PORT}`); });