Spaces:
Running
Running
| 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}`); | |
| }); | |