| 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); |
| }); |
|
|