mrpoddaa commited on
Commit
ec7d71c
Β·
verified Β·
1 Parent(s): dceb706

Upload 5 files

Browse files
Files changed (5) hide show
  1. .dockerignore +7 -0
  2. DEPLOYMENT.md +171 -0
  3. Dockerfile +20 -0
  4. package.json +20 -0
  5. server.js +307 -0
.dockerignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ npm-debug.log
3
+ uploads/*
4
+ .git
5
+ .gitignore
6
+ README.md
7
+ .env
DEPLOYMENT.md ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Deployment - Step by Step
2
+
3
+ ## Step 1: Hugging Face Account ΰΆ‘ΰΆšΰΆšΰ·Š ΰ·„ΰΆ―ΰ·ΰΆœΰΆ±ΰ·ŠΰΆ±
4
+
5
+ 1. https://huggingface.co/join ΰ·€ΰ·™ΰΆΆΰ·Š ΰΆ…ΰΆ©ΰ·€ΰ·’ΰΆΊΰΆ§ ࢺࢱ්ࢱ
6
+ 2. Email ΰΆ‘ΰΆšΰΆšΰ·Š ࢯාࢽා Sign Up ࢚ࢻࢱ්ࢱ
7
+ 3. Email ΰΆ‘ΰΆšΰ·š verification link ΰΆ‘ΰΆš click ࢚ࢻࢱ්ࢱ
8
+ 4. Login ΰ·€ΰ·™ΰΆ±ΰ·ŠΰΆ±
9
+
10
+ ## Step 2: ΰΆ±ΰ·€ Space ΰΆ‘ΰΆšΰΆšΰ·Š Create ࢚ࢻࢱ්ࢱ
11
+
12
+ 1. Profile picture ΰΆ‘ΰΆš click ࢚ࢻࢱ්ࢱ (top right corner)
13
+ 2. **"New Space"** button ΰΆ‘ΰΆš click ࢚ࢻࢱ්ࢱ
14
+ 3. ΰ·€ΰ·’ΰ·ƒΰ·ŠΰΆ­ΰΆ» fill ࢚ࢻࢱ්ࢱ:
15
+ - **Space name**: `dulaksha-stream` (හෝ ΰΆ•ΰΆ± ࢱࢸ්࢚)
16
+ - **License**: MIT
17
+ - **Select SDK**: **Docker** (ࢸේ࢚ select ࢚ࢻࢱ්ࢱ ΰΆ•ΰΆ±ΰ·š)
18
+ - **Space hardware**: CPU basic - Free
19
+ 4. **"Create Space"** click ࢚ࢻࢱ්ࢱ
20
+
21
+ ## Step 3: Files Upload ࢚ࢻࢱ්ࢱ
22
+
23
+ ### Method 1: Web Interface ΰΆ‘ΰΆšΰ·™ΰΆ±ΰ·Š (Easy)
24
+
25
+ 1. **"Files"** tab ΰΆ‘ΰΆš click ࢚ࢻࢱ්ࢱ
26
+ 2. **"Add file"** > **"Upload files"** click ࢚ࢻࢱ්ࢱ
27
+ 3. ࢸේ files upload ࢚ࢻࢱ්ࢱ ΰΆ‘ΰΆšΰ·’ΰΆ±ΰ·Š ΰΆ‘ΰΆš:
28
+ - `server.js`
29
+ - `package.json`
30
+ - `Dockerfile`
31
+ - `.dockerignore`
32
+ 4. Upload වෙࢽා ࢱැࢭි files ΰΆ­ΰ·’ΰΆΊΰ·™ΰΆ±ΰ·€ΰΆ±ΰΆΈΰ·Š repeat ࢚ࢻࢱ්ࢱ
33
+
34
+ ### Method 2: Git ΰΆ‘ΰΆšΰ·™ΰΆ±ΰ·Š (Advanced)
35
+
36
+ ```bash
37
+ git clone https://huggingface.co/spaces/YOUR-USERNAME/dulaksha-stream
38
+ cd dulaksha-stream
39
+ # Copy all files to this folder
40
+ git add .
41
+ git commit -m "Initial commit"
42
+ git push
43
+ ```
44
+
45
+ ## Step 4: Build ΰΆ‘ΰΆš Wait ࢚ࢻࢱ්ࢱ
46
+
47
+ 1. **"App"** tab ΰΆ‘ΰΆšΰΆ§ ࢺࢱ්ࢱ
48
+ 2. **"Building"** status ΰΆ‘ΰΆš ΰΆ΄ΰ·™ΰΆ±ΰ·ŠΰΆ±ΰΆ± ΰΆ‘ΰΆš ࢢࢽࢱ්ࢱ
49
+ 3. **5-10 minutes** ΰ·€ΰ·’ΰΆ­ΰΆ» wait ࢚ࢻࢱ්ࢱ
50
+ 4. **"Running"** ΰΆšΰ·’ΰΆΊΰΆ½ΰ· status ΰΆ‘ΰΆš change ΰ·€ΰ·™ΰΆ±ΰΆšΰΆ½ΰ·Š wait ࢚ࢻࢱ්ࢱ
51
+
52
+ Build ΰΆ‘ΰΆš fail ΰ·€ΰ·™ΰΆ±ΰ·€ΰΆ±ΰΆΈΰ·Š:
53
+ - **"Logs"** tab ΰΆ‘ΰΆš check ࢚ࢻࢱ්ࢱ
54
+ - Error message ΰΆ‘ΰΆš copy ࢚ࢻࢱ්ࢱ
55
+
56
+ ## Step 5: URL ΰΆ‘ΰΆš ࢜ࢱ්ࢱ
57
+
58
+ Build ΰΆ‘ΰΆš success වුࢫාࢸ ΰΆ”ΰΆΊΰ·ΰΆœΰ·š app ΰΆ‘ΰΆš ࢸේ URL ΰΆ‘ΰΆšΰ·š available ΰ·€ΰ·™ΰΆΊΰ·’:
59
+
60
+ ```
61
+ https://YOUR-USERNAME-dulaksha-stream.hf.space
62
+ ```
63
+
64
+ ࢋࢯාහࢻࢫ:
65
+ ```
66
+ https://dulaksha-dulaksha-stream.hf.space
67
+ ```
68
+
69
+ ## Step 6: Test ࢚ࢻࢱ්ࢱ
70
+
71
+ ### Browser ΰΆ‘ΰΆšΰ·™ΰΆ±ΰ·Š Test:
72
+ 1. URL ΰΆ‘ΰΆš browser ΰΆ‘ΰΆšΰ·š open ࢚ࢻࢱ්ࢱ
73
+ 2. Server running ΰΆšΰ·’ΰΆΊΰΆ½ΰ· ΰΆ΄ΰ·™ΰΆ±ΰ·Šΰ·€ΰΆ±ΰ·ŠΰΆ± ΰΆ•ΰΆ±
74
+
75
+ ### Video Upload Test (Postman):
76
+
77
+ 1. Postman open ࢚ࢻࢱ්ࢱ
78
+ 2. **New Request** create ࢚ࢻࢱ්ࢱ
79
+ 3. Settings:
80
+ - **Method**: POST
81
+ - **URL**: `https://YOUR-USERNAME-dulaksha-stream.hf.space/upload`
82
+ - **Body**: form-data
83
+ - **Key**: `file` (type: File)
84
+ - **Value**: video file ΰΆ‘ΰΆšΰΆšΰ·Š select ࢚ࢻࢱ්ࢱ
85
+ 4. **Send** click ࢚ࢻࢱ්ࢱ
86
+
87
+ ### cURL ΰΆ‘ΰΆšΰ·™ΰΆ±ΰ·Š Test:
88
+
89
+ ```bash
90
+ curl -X POST -F "file=@video.mp4" https://YOUR-USERNAME-dulaksha-stream.hf.space/upload
91
+ ```
92
+
93
+ ## Step 7: Response ΰΆ‘ΰΆš Check ࢚ࢻࢱ්ࢱ
94
+
95
+ Upload success වුࢫාࢸ ࢸේ ΰ·€ΰ·’ΰΆ―ΰ·’ΰ·„ΰ·š response ΰΆ‘ΰΆšΰΆšΰ·Š ࢑ࢱවා:
96
+
97
+ ```json
98
+ {
99
+ "status": "success",
100
+ "stream_link": "https://YOUR-SPACE.hf.space/stream/123456/video.mp4",
101
+ "qualities": ["1080p", "720p", "480p"]
102
+ }
103
+ ```
104
+
105
+ ## Video Quality Conversion
106
+
107
+ - **2160p (4K) video**: Convert to 1080p, 720p, 480p
108
+ - **1080p video**: Convert to 1080p, 720p, 480p
109
+ - **720p video**: Convert to 720p, 480p
110
+ - **480p video**: Keep as 480p
111
+
112
+ ## API Endpoints
113
+
114
+ ### Upload Video
115
+ ```
116
+ POST /upload
117
+ Content-Type: multipart/form-data
118
+ Body: file (video file)
119
+ ```
120
+
121
+ ### Stream File
122
+ ```
123
+ GET /stream/:id/:filename
124
+ ```
125
+
126
+ ### Download File
127
+ ```
128
+ GET /download/:id/:filename
129
+ ```
130
+
131
+ ## Important Notes
132
+
133
+ - **Bot connect ΰ·€ΰ·™ΰΆ±ΰ·ŠΰΆ± විࢱාࢩි 2-3්࢚ ࢺࢱවා** - First upload ΰΆ‘ΰΆšΰΆ§ wait ࢚ࢻࢱ්ࢱ
134
+ - **Large files ΰ·€ΰΆ½ΰΆ§ processing time වැࢩිࢺි** - 4K videos ΰ·€ΰΆ½ΰΆ§ 10-20 minutes ࢺࢱවා
135
+ - **Stream links permanent** - ΰΆšΰ·€ΰΆ―ΰ·ΰ·€ΰΆ­ΰ·Š expire ΰ·€ΰ·™ΰΆ±ΰ·ŠΰΆ±ΰ·š ࢱැහැ
136
+ - **All files Telegram ΰΆ‘ΰΆšΰ·š store වෙࢱවා** - Backup ΰΆ‘ΰΆšΰΆšΰ·Š ΰ·€ΰ·’ΰΆ―ΰ·’ΰ·„ΰΆ§
137
+
138
+ ## Common Issues
139
+
140
+ ### 1. Build Failed
141
+ **Solution**: Dockerfile ΰΆ‘ΰΆš ΰ·„ΰΆ»ΰ·’ΰΆΊΰΆ§ΰΆΈ copy වෙࢽා ΰΆ­ΰ·’ΰΆΊΰ·™ΰΆ±ΰ·€ΰΆ― check ࢚ࢻࢱ්ࢱ
142
+
143
+ ### 2. Service Unavailable
144
+ **Solution**: විࢱාࢩි 2-3්࢚ wait ࢚ࢻࢽා retry ࢚ࢻࢱ්ࢱ (bot connecting)
145
+
146
+ ### 3. Upload Timeout
147
+ **Solution**:
148
+ - File size ΰΆ…ΰΆ©ΰ·” ࢚ࢻࢱ්ࢱ
149
+ - Better hardware tier ΰΆ‘ΰΆšΰΆšΰ·Š select ࢚ࢻࢱ්ࢱ (Settings > Hardware)
150
+
151
+ ### 4. FFmpeg Error
152
+ **Solution**: Video file ΰΆ‘ΰΆš corrupt වෙࢽා ࢱැࢯ්ࢯ check ࢚ࢻࢱ්ࢱ
153
+
154
+ ## Hardware Upgrade (Optional)
155
+
156
+ Free tier slow ࢱࢸ්:
157
+ 1. Space Settings > Hardware
158
+ 2. Upgrade ࢚ࢻࢱ්ࢱ:
159
+ - CPU basic (Free) β†’ CPU upgrade ($0.60/hour)
160
+ - GPU ΰΆ‘ΰΆšΰΆšΰ·Š select ࢚ࢻࢱ්ࢱ (faster conversion)
161
+
162
+ ## Files ΰΆ±ΰ·™ΰΆ»ΰΆ΄ΰΆ±ΰ·ŠΰΆ± ΰΆ•ΰΆ±ΰ·š ࢱࢸ්
163
+
164
+ ```bash
165
+ # Hugging Face Space ΰΆ‘ΰΆšΰ·š
166
+ Files > Delete ࢚ࢻࢱ්ࢱ files
167
+ ```
168
+
169
+ ---
170
+
171
+ **ΰ·ƒΰ·’ΰΆΊΰΆ½ΰ·” files ࢸේ folder ΰΆ‘ΰΆšΰ·š ࢭිࢺෙࢱවා. Upload ࢚ࢻࢱ්ࢱ ΰ·€ΰ·’ΰΆ­ΰΆ»ΰΆΊΰ·’ ΰΆ•ΰΆ±!**
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18-slim
2
+
3
+ RUN apt-get update && apt-get install -y \
4
+ ffmpeg \
5
+ python3 \
6
+ && rm -rf /var/lib/apt/lists/*
7
+
8
+ WORKDIR /app
9
+
10
+ COPY package*.json ./
11
+
12
+ RUN npm install
13
+
14
+ COPY . .
15
+
16
+ RUN mkdir -p uploads
17
+
18
+ EXPOSE 7860
19
+
20
+ CMD ["npm", "start"]
package.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "dulaksha-stream",
3
+ "version": "1.0.0",
4
+ "description": "Video streaming service with multi-quality HLS support",
5
+ "main": "server.js",
6
+ "scripts": {
7
+ "start": "node server.js"
8
+ },
9
+ "dependencies": {
10
+ "express": "^4.18.2",
11
+ "multer": "^1.4.5-lts.1",
12
+ "telegram": "^2.22.2",
13
+ "input": "^1.0.1",
14
+ "fluent-ffmpeg": "^2.1.2",
15
+ "ffmpeg-static": "^5.2.0"
16
+ },
17
+ "engines": {
18
+ "node": ">=18.0.0"
19
+ }
20
+ }
server.js ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require("express");
2
+ const multer = require("multer");
3
+ const { TelegramClient } = require("telegram");
4
+ const { StringSession } = require("telegram/sessions");
5
+ const input = require("input");
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const ffmpeg = require("fluent-ffmpeg");
9
+ const ffmpegPath = require("ffmpeg-static");
10
+
11
+ ffmpeg.setFfmpegPath(ffmpegPath);
12
+
13
+ const app = express();
14
+ const port = process.env.PORT || 7860;
15
+
16
+ const API_ID = 24947404;
17
+ const API_HASH = "a27b63b5e9a4c4e9279d1db641aca338";
18
+ const BOT_TOKEN = "8300006322:AAHnZf0xlt5mrzlaBjXYmVceZwo3xmwTZaA";
19
+ const BIN_CHANNEL = -1003569314973;
20
+ const SYSTEM_URL = process.env.SPACE_HOST
21
+ ? `https://${process.env.SPACE_ID.replace('/', '-')}-${process.env.SPACE_HOST}`
22
+ : "http://localhost:7860";
23
+
24
+ const clients = [];
25
+ const upload = multer({ dest: "uploads/" });
26
+
27
+ app.use(express.json());
28
+
29
+ async function getNextClient() {
30
+ const stringSession = new StringSession("");
31
+ const client = new TelegramClient(stringSession, API_ID, API_HASH, {
32
+ connectionRetries: 5,
33
+ });
34
+
35
+ await client.start({
36
+ botAuthToken: BOT_TOKEN,
37
+ });
38
+
39
+ clients.push(client);
40
+ return client;
41
+ }
42
+
43
+ async function startAllBots() {
44
+ console.log("Starting Dulaksha Stream bot...");
45
+ await getNextClient();
46
+ console.log("Bot connected successfully!");
47
+ }
48
+
49
+ function getVideoResolution(inputPath) {
50
+ return new Promise((resolve, reject) => {
51
+ ffmpeg.ffprobe(inputPath, (err, metadata) => {
52
+ if (err) return reject(err);
53
+ const videoStream = metadata.streams.find(s => s.codec_type === 'video');
54
+ if (!videoStream) return reject(new Error("No video stream found"));
55
+ resolve({ width: videoStream.width, height: videoStream.height });
56
+ });
57
+ });
58
+ }
59
+
60
+ function convertToMultiQualityHLS(inputPath, outputDir) {
61
+ return new Promise(async (resolve, reject) => {
62
+ try {
63
+ const resolution = await getVideoResolution(inputPath);
64
+ const height = resolution.height;
65
+
66
+ const qualities = [];
67
+ if (height >= 2160) {
68
+ qualities.push({ name: "1080p", height: 1080, bitrate: "5000k" });
69
+ qualities.push({ name: "720p", height: 720, bitrate: "2800k" });
70
+ qualities.push({ name: "480p", height: 480, bitrate: "1400k" });
71
+ } else if (height >= 1080) {
72
+ qualities.push({ name: "1080p", height: 1080, bitrate: "5000k" });
73
+ qualities.push({ name: "720p", height: 720, bitrate: "2800k" });
74
+ qualities.push({ name: "480p", height: 480, bitrate: "1400k" });
75
+ } else if (height >= 720) {
76
+ qualities.push({ name: "720p", height: 720, bitrate: "2800k" });
77
+ qualities.push({ name: "480p", height: 480, bitrate: "1400k" });
78
+ } else {
79
+ qualities.push({ name: "480p", height: 480, bitrate: "1400k" });
80
+ }
81
+
82
+ const masterPlaylist = path.join(outputDir, "master.m3u8");
83
+ let masterContent = "#EXTM3U\n#EXT-X-VERSION:3\n";
84
+
85
+ const conversionPromises = qualities.map((quality, index) => {
86
+ return new Promise((res, rej) => {
87
+ const qualityDir = path.join(outputDir, quality.name);
88
+ fs.mkdirSync(qualityDir, { recursive: true });
89
+ const playlistPath = path.join(qualityDir, "index.m3u8");
90
+
91
+ ffmpeg(inputPath)
92
+ .outputOptions([
93
+ `-vf scale=-2:${quality.height}`,
94
+ `-c:v libx264`,
95
+ `-b:v ${quality.bitrate}`,
96
+ `-c:a aac`,
97
+ `-b:a 128k`,
98
+ `-start_number 0`,
99
+ `-hls_time 10`,
100
+ `-hls_list_size 0`,
101
+ `-f hls`
102
+ ])
103
+ .output(playlistPath)
104
+ .on("end", () => {
105
+ masterContent += `#EXT-X-STREAM-INF:BANDWIDTH=${parseInt(quality.bitrate) * 1000},RESOLUTION=${Math.floor(quality.height * 16 / 9)}x${quality.height}\n`;
106
+ masterContent += `${quality.name}/index.m3u8\n`;
107
+ res();
108
+ })
109
+ .on("error", rej)
110
+ .run();
111
+ });
112
+ });
113
+
114
+ await Promise.all(conversionPromises);
115
+
116
+ fs.writeFileSync(masterPlaylist, masterContent);
117
+ resolve(masterPlaylist);
118
+
119
+ } catch (err) {
120
+ reject(err);
121
+ }
122
+ });
123
+ }
124
+
125
+ async function uploadHLSFiles(client, hlsDir, originalName) {
126
+ const uploadedFiles = { qualities: {} };
127
+
128
+ const masterPath = path.join(hlsDir, "master.m3u8");
129
+ const masterResult = await client.sendFile(BIN_CHANNEL, {
130
+ file: masterPath,
131
+ caption: `πŸ“‚ ${originalName} - master.m3u8`,
132
+ forceDocument: true
133
+ });
134
+ uploadedFiles.master = masterResult.id;
135
+
136
+ const qualityDirs = fs.readdirSync(hlsDir).filter(f =>
137
+ fs.statSync(path.join(hlsDir, f)).isDirectory()
138
+ );
139
+
140
+ for (const qualityDir of qualityDirs) {
141
+ const qualityPath = path.join(hlsDir, qualityDir);
142
+ const files = fs.readdirSync(qualityPath);
143
+
144
+ const m3u8File = files.find(f => f.endsWith(".m3u8"));
145
+ const tsFiles = files.filter(f => f.endsWith(".ts"));
146
+
147
+ uploadedFiles.qualities[qualityDir] = { segments: [] };
148
+
149
+ const m3u8Path = path.join(qualityPath, m3u8File);
150
+ const m3u8Result = await client.sendFile(BIN_CHANNEL, {
151
+ file: m3u8Path,
152
+ caption: `πŸ“‚ ${originalName} - ${qualityDir}/index.m3u8`,
153
+ forceDocument: true
154
+ });
155
+ uploadedFiles.qualities[qualityDir].index = m3u8Result.id;
156
+
157
+ for (const tsFile of tsFiles) {
158
+ const tsPath = path.join(qualityPath, tsFile);
159
+ const tsResult = await client.sendFile(BIN_CHANNEL, {
160
+ file: tsPath,
161
+ caption: `πŸ“‚ ${originalName} - ${qualityDir}/${tsFile}`,
162
+ forceDocument: true
163
+ });
164
+ uploadedFiles.qualities[qualityDir].segments.push({
165
+ name: tsFile,
166
+ id: tsResult.id
167
+ });
168
+ }
169
+ }
170
+
171
+ return uploadedFiles;
172
+ }
173
+
174
+ app.post("/upload", upload.single('file'), async (req, res) => {
175
+ if (!req.file) return res.status(400).json({ error: "No file" });
176
+
177
+ const originalName = req.file.originalname;
178
+ const safeName = encodeURIComponent(originalName.replace(/\s+/g, '_'));
179
+ const extension = path.extname(originalName).toLowerCase();
180
+ const tempPath = req.file.path;
181
+ const newPath = tempPath + extension;
182
+
183
+ const videoExtensions = ['.mp4', '.mkv', '.avi', '.mov', '.wmv', '.flv', '.webm'];
184
+ const isVideo = videoExtensions.includes(extension);
185
+
186
+ try {
187
+ fs.renameSync(tempPath, newPath);
188
+ const client = clients[0] || await getNextClient();
189
+ if (!client) throw new Error("Server Busy");
190
+
191
+ if (isVideo) {
192
+ const hlsDir = path.join("uploads", `hls_${Date.now()}`);
193
+ fs.mkdirSync(hlsDir, { recursive: true });
194
+
195
+ await convertToMultiQualityHLS(newPath, hlsDir);
196
+ const hlsFiles = await uploadHLSFiles(client, hlsDir, originalName);
197
+
198
+ fs.rmSync(hlsDir, { recursive: true, force: true });
199
+ if (fs.existsSync(newPath)) fs.unlinkSync(newPath);
200
+
201
+ res.json({
202
+ status: "success",
203
+ stream_link: `${SYSTEM_URL}/stream/${hlsFiles.master}/${safeName}`,
204
+ qualities: Object.keys(hlsFiles.qualities)
205
+ });
206
+ } else {
207
+ const result = await client.sendFile(BIN_CHANNEL, {
208
+ file: newPath,
209
+ caption: `πŸ“‚ Uploaded: ${originalName}`,
210
+ forceDocument: false
211
+ });
212
+
213
+ if (fs.existsSync(newPath)) fs.unlinkSync(newPath);
214
+
215
+ res.json({
216
+ status: "success",
217
+ link: `${SYSTEM_URL}/stream/${result.id}`,
218
+ stream_link: `${SYSTEM_URL}/stream/${result.id}/${safeName}`,
219
+ download_link: `${SYSTEM_URL}/download/${result.id}/${safeName}`
220
+ });
221
+ }
222
+ } catch (e) {
223
+ console.error("Upload error:", e);
224
+ res.status(500).json({ error: e.message });
225
+ if (fs.existsSync(newPath)) fs.unlinkSync(newPath);
226
+ else if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
227
+ }
228
+ });
229
+
230
+ app.get("/stream/:id/:filename?", async (req, res) => {
231
+ try {
232
+ const messageId = parseInt(req.params.id);
233
+ const client = clients[0];
234
+ if (!client) return res.status(503).send("Service unavailable");
235
+
236
+ const messages = await client.getMessages(BIN_CHANNEL, { ids: [messageId] });
237
+ const message = messages[0];
238
+
239
+ if (!message || !message.media) {
240
+ return res.status(404).send("File not found");
241
+ }
242
+
243
+ const fileSize = message.media.document.size;
244
+ const range = req.headers.range;
245
+
246
+ if (range) {
247
+ const parts = range.replace(/bytes=/, "").split("-");
248
+ const start = parseInt(parts[0], 10);
249
+ const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
250
+ const chunkSize = (end - start) + 1;
251
+
252
+ res.writeHead(206, {
253
+ "Content-Range": `bytes ${start}-${end}/${fileSize}`,
254
+ "Accept-Ranges": "bytes",
255
+ "Content-Length": chunkSize,
256
+ "Content-Type": "application/octet-stream"
257
+ });
258
+
259
+ const buffer = await client.downloadMedia(message, {
260
+ offset: start,
261
+ limit: chunkSize
262
+ });
263
+ res.end(buffer);
264
+ } else {
265
+ res.writeHead(200, {
266
+ "Content-Length": fileSize,
267
+ "Content-Type": "application/octet-stream"
268
+ });
269
+
270
+ const buffer = await client.downloadMedia(message);
271
+ res.end(buffer);
272
+ }
273
+ } catch (e) {
274
+ console.error("Stream error:", e);
275
+ res.status(500).send("Error streaming file");
276
+ }
277
+ });
278
+
279
+ app.get("/download/:id/:filename", async (req, res) => {
280
+ try {
281
+ const messageId = parseInt(req.params.id);
282
+ const filename = decodeURIComponent(req.params.filename);
283
+ const client = clients[0];
284
+ if (!client) return res.status(503).send("Service unavailable");
285
+
286
+ const messages = await client.getMessages(BIN_CHANNEL, { ids: [messageId] });
287
+ const message = messages[0];
288
+
289
+ if (!message || !message.media) {
290
+ return res.status(404).send("File not found");
291
+ }
292
+
293
+ res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
294
+ res.setHeader("Content-Type", "application/octet-stream");
295
+
296
+ const buffer = await client.downloadMedia(message);
297
+ res.end(buffer);
298
+ } catch (e) {
299
+ console.error("Download error:", e);
300
+ res.status(500).send("Error downloading file");
301
+ }
302
+ });
303
+
304
+ app.listen(port, () => {
305
+ console.log(`Dulaksha Stream Server Running on Port ${port}`);
306
+ startAllBots().catch(console.error);
307
+ });