|
|
| 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(); |
|
|
| |
| 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; |
|
|
| |
| 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."); |
| } |
| |
| |
| 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}`); |
| }); |
|
|