stream / server.js
mrpoddaa's picture
Update server.js
8db3d06 verified
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);
});