const express = require("express"); const multer = require("multer"); const { TelegramClient } = require("telegram"); const { StringSession } = require("telegram/sessions"); const input = require("input"); const fs = require("fs"); const path = require("path"); const ffmpeg = require("fluent-ffmpeg"); const ffmpegPath = require("ffmpeg-static"); ffmpeg.setFfmpegPath(ffmpegPath); const app = express(); const port = process.env.PORT || 7860; const API_ID = 21436919; const API_HASH = "6f289f8dccefd28e2d8077fd05568004"; const BOT_TOKEN = "8300006322:AAHnZf0xlt5mrzlaBjXYmVceZwo3xmwTZaA"; const BIN_CHANNEL = -1003569314973; const SYSTEM_URL = process.env.SPACE_HOST ? `https://${process.env.SPACE_ID.replace('/', '-')}-${process.env.SPACE_HOST}` : "http://localhost:7860"; const clients = []; const upload = multer({ dest: "uploads/" }); app.use(express.json()); async function getNextClient() { const stringSession = new StringSession(""); const client = new TelegramClient(stringSession, API_ID, API_HASH, { connectionRetries: 5, }); await client.start({ botAuthToken: BOT_TOKEN, }); clients.push(client); return client; } async function startAllBots() { console.log("Starting Dulaksha Stream bot..."); await getNextClient(); console.log("Bot connected successfully!"); } function getVideoResolution(inputPath) { return new Promise((resolve, reject) => { ffmpeg.ffprobe(inputPath, (err, metadata) => { if (err) return reject(err); const videoStream = metadata.streams.find(s => s.codec_type === 'video'); if (!videoStream) return reject(new Error("No video stream found")); resolve({ width: videoStream.width, height: videoStream.height }); }); }); } function convertToMultiQualityHLS(inputPath, outputDir) { return new Promise(async (resolve, reject) => { try { const resolution = await getVideoResolution(inputPath); const height = resolution.height; const qualities = []; if (height >= 2160) { qualities.push({ name: "1080p", height: 1080, bitrate: "5000k" }); qualities.push({ name: "720p", height: 720, bitrate: "2800k" }); qualities.push({ name: "480p", height: 480, bitrate: "1400k" }); } else if (height >= 1080) { qualities.push({ name: "1080p", height: 1080, bitrate: "5000k" }); qualities.push({ name: "720p", height: 720, bitrate: "2800k" }); qualities.push({ name: "480p", height: 480, bitrate: "1400k" }); } else if (height >= 720) { qualities.push({ name: "720p", height: 720, bitrate: "2800k" }); qualities.push({ name: "480p", height: 480, bitrate: "1400k" }); } else { qualities.push({ name: "480p", height: 480, bitrate: "1400k" }); } const masterPlaylist = path.join(outputDir, "master.m3u8"); let masterContent = "#EXTM3U\n#EXT-X-VERSION:3\n"; const conversionPromises = qualities.map((quality, index) => { return new Promise((res, rej) => { const qualityDir = path.join(outputDir, quality.name); fs.mkdirSync(qualityDir, { recursive: true }); const playlistPath = path.join(qualityDir, "index.m3u8"); ffmpeg(inputPath) .outputOptions([ `-vf scale=-2:${quality.height}`, `-c:v libx264`, `-b:v ${quality.bitrate}`, `-c:a aac`, `-b:a 128k`, `-start_number 0`, `-hls_time 10`, `-hls_list_size 0`, `-f hls` ]) .output(playlistPath) .on("end", () => { masterContent += `#EXT-X-STREAM-INF:BANDWIDTH=${parseInt(quality.bitrate) * 1000},RESOLUTION=${Math.floor(quality.height * 16 / 9)}x${quality.height}\n`; masterContent += `${quality.name}/index.m3u8\n`; res(); }) .on("error", rej) .run(); }); }); await Promise.all(conversionPromises); fs.writeFileSync(masterPlaylist, masterContent); resolve(masterPlaylist); } catch (err) { reject(err); } }); } async function uploadHLSFiles(client, hlsDir, originalName) { const uploadedFiles = { qualities: {} }; const masterPath = path.join(hlsDir, "master.m3u8"); const masterResult = await client.sendFile(BIN_CHANNEL, { file: masterPath, caption: `📂 ${originalName} - master.m3u8`, forceDocument: true }); uploadedFiles.master = masterResult.id; const qualityDirs = fs.readdirSync(hlsDir).filter(f => fs.statSync(path.join(hlsDir, f)).isDirectory() ); for (const qualityDir of qualityDirs) { const qualityPath = path.join(hlsDir, qualityDir); const files = fs.readdirSync(qualityPath); const m3u8File = files.find(f => f.endsWith(".m3u8")); const tsFiles = files.filter(f => f.endsWith(".ts")); uploadedFiles.qualities[qualityDir] = { segments: [] }; const m3u8Path = path.join(qualityPath, m3u8File); const m3u8Result = await client.sendFile(BIN_CHANNEL, { file: m3u8Path, caption: `📂 ${originalName} - ${qualityDir}/index.m3u8`, forceDocument: true }); uploadedFiles.qualities[qualityDir].index = m3u8Result.id; for (const tsFile of tsFiles) { const tsPath = path.join(qualityPath, tsFile); const tsResult = await client.sendFile(BIN_CHANNEL, { file: tsPath, caption: `📂 ${originalName} - ${qualityDir}/${tsFile}`, forceDocument: true }); uploadedFiles.qualities[qualityDir].segments.push({ name: tsFile, id: tsResult.id }); } } return uploadedFiles; } app.post("/upload", upload.single('file'), async (req, res) => { if (!req.file) return res.status(400).json({ error: "No file" }); const originalName = req.file.originalname; const safeName = encodeURIComponent(originalName.replace(/\s+/g, '_')); const extension = path.extname(originalName).toLowerCase(); const tempPath = req.file.path; const newPath = tempPath + extension; const videoExtensions = ['.mp4', '.mkv', '.avi', '.mov', '.wmv', '.flv', '.webm']; const isVideo = videoExtensions.includes(extension); try { fs.renameSync(tempPath, newPath); const client = clients[0] || await getNextClient(); if (!client) throw new Error("Server Busy"); if (isVideo) { const hlsDir = path.join("uploads", `hls_${Date.now()}`); fs.mkdirSync(hlsDir, { recursive: true }); await convertToMultiQualityHLS(newPath, hlsDir); const hlsFiles = await uploadHLSFiles(client, hlsDir, originalName); fs.rmSync(hlsDir, { recursive: true, force: true }); if (fs.existsSync(newPath)) fs.unlinkSync(newPath); res.json({ status: "success", stream_link: `${SYSTEM_URL}/stream/${hlsFiles.master}/${safeName}`, qualities: Object.keys(hlsFiles.qualities) }); } else { const result = await client.sendFile(BIN_CHANNEL, { file: newPath, caption: `📂 Uploaded: ${originalName}`, forceDocument: false }); if (fs.existsSync(newPath)) fs.unlinkSync(newPath); res.json({ status: "success", link: `${SYSTEM_URL}/stream/${result.id}`, stream_link: `${SYSTEM_URL}/stream/${result.id}/${safeName}`, download_link: `${SYSTEM_URL}/download/${result.id}/${safeName}` }); } } catch (e) { console.error("Upload error:", e); res.status(500).json({ error: e.message }); if (fs.existsSync(newPath)) fs.unlinkSync(newPath); else if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } }); app.get("/stream/:id/:filename?", async (req, res) => { try { const messageId = parseInt(req.params.id); const client = clients[0]; if (!client) return res.status(503).send("Service unavailable"); const messages = await client.getMessages(BIN_CHANNEL, { ids: [messageId] }); const message = messages[0]; if (!message || !message.media) { return res.status(404).send("File not found"); } const fileSize = message.media.document.size; const range = req.headers.range; if (range) { const parts = range.replace(/bytes=/, "").split("-"); const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1; const chunkSize = (end - start) + 1; res.writeHead(206, { "Content-Range": `bytes ${start}-${end}/${fileSize}`, "Accept-Ranges": "bytes", "Content-Length": chunkSize, "Content-Type": "application/octet-stream" }); const buffer = await client.downloadMedia(message, { offset: start, limit: chunkSize }); res.end(buffer); } else { res.writeHead(200, { "Content-Length": fileSize, "Content-Type": "application/octet-stream" }); const buffer = await client.downloadMedia(message); res.end(buffer); } } catch (e) { console.error("Stream error:", e); res.status(500).send("Error streaming file"); } }); app.get("/download/:id/:filename", async (req, res) => { try { const messageId = parseInt(req.params.id); const filename = decodeURIComponent(req.params.filename); const client = clients[0]; if (!client) return res.status(503).send("Service unavailable"); const messages = await client.getMessages(BIN_CHANNEL, { ids: [messageId] }); const message = messages[0]; if (!message || !message.media) { return res.status(404).send("File not found"); } res.setHeader("Content-Disposition", `attachment; filename="${filename}"`); res.setHeader("Content-Type", "application/octet-stream"); const buffer = await client.downloadMedia(message); res.end(buffer); } catch (e) { console.error("Download error:", e); res.status(500).send("Error downloading file"); } }); app.listen(port, () => { console.log(`Dulaksha Stream Server Running on Port ${port}`); startAllBots().catch(console.error); });