File size: 10,839 Bytes
6af76e3
2d70f5d
 
 
7e95f4d
 
 
2d70f5d
c64c251
7e95f4d
6af76e3
857e272
7e95f4d
2d70f5d
857e272
 
7e95f4d
 
857e272
 
 
7e95f4d
2d70f5d
857e272
 
 
6af76e3
 
857e272
2d70f5d
 
 
6af76e3
857e272
7e95f4d
 
 
 
 
 
 
 
 
2d70f5d
 
857e272
2d70f5d
857e272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e95f4d
 
857e272
 
 
 
 
 
 
 
 
 
 
 
7e95f4d
857e272
 
 
 
 
 
6af76e3
 
 
857e272
7e95f4d
 
857e272
 
 
 
 
 
7e95f4d
857e272
 
 
 
7e95f4d
857e272
7e95f4d
 
 
 
 
857e272
 
 
7e95f4d
6af76e3
2d70f5d
 
857e272
 
 
6af76e3
2d70f5d
857e272
2d70f5d
7e95f4d
2d70f5d
857e272
7e95f4d
857e272
7e95f4d
 
 
 
 
857e272
7e95f4d
 
 
 
 
857e272
7e95f4d
 
857e272
7e95f4d
857e272
 
7e95f4d
 
857e272
 
 
 
7e95f4d
857e272
7e95f4d
 
 
 
 
2d70f5d
 
857e272
7e95f4d
 
 
 
 
857e272
 
 
 
 
 
 
 
7e95f4d
 
 
2d70f5d
 
7e95f4d
 
 
857e272
7e95f4d
 
 
 
2d70f5d
 
7e95f4d
 
 
 
 
2d70f5d
7e95f4d
2d70f5d
6af76e3
 
857e272
7e95f4d
857e272
 
 
 
 
 
 
 
 
2d70f5d
857e272
 
 
 
 
 
 
 
 
 
 
7e95f4d
857e272
 
 
7e95f4d
 
857e272
7e95f4d
 
2d70f5d
857e272
7e95f4d
857e272
 
 
 
 
 
 
 
 
6af76e3
857e272
 
 
 
 
 
2d70f5d
857e272
 
 
7e95f4d
 
857e272
2d70f5d
 
 
857e272
7e95f4d
857e272
 
 
 
 
 
 
 
 
 
 
 
7e95f4d
857e272
7e95f4d
857e272
 
 
2d70f5d
6af76e3
857e272
7e95f4d
857e272
 
 
 
 
 
 
 
7e95f4d
857e272
 
7e95f4d
 
857e272
 
 
 
 
 
 
 
 
 
 
 
 
 
6af76e3
 
857e272
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
const TelegramBot = require('node-telegram-bot-api');
const { spawn } = require('child_process');
const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;
const os = require('os');
const fs = require('fs');
const path = require('path');
const axios = require('axios');

const token = '8141535657:AAGYH7PFD8vjbd5Ty6IJn2r8yBRpdSVEbQg';
const ADMIN_USER_ID = '7708913693';

const WATERMARK_DIR = path.join(__dirname, 'watermarks');
if (!fs.existsSync(WATERMARK_DIR)) fs.mkdirSync(WATERMARK_DIR);

const activeStreams = new Map();

const bot = new TelegramBot(token, { polling: true });

// Logger with timestamp
function log(message) {
  console.log(`[${new Date().toISOString()}] ${message}`);
}

// Check admin
function isAdmin(userId) {
  return userId.toString() === ADMIN_USER_ID;
}

// Generate 4-digit stream ID
function generateStreamId() {
  return Math.floor(1000 + Math.random() * 9000).toString();
}

// Download watermark image from URL and save
async function downloadWatermark(url, name) {
  const filePath = path.join(WATERMARK_DIR, `${name}.png`);
  const response = await axios({ url, method: 'GET', responseType: 'stream' });
  const writer = fs.createWriteStream(filePath);
  response.data.pipe(writer);
  return new Promise((resolve, reject) => {
    writer.on('finish', () => resolve(filePath));
    writer.on('error', reject);
  });
}

// /start command
bot.onText(/\/start/, (msg) => {
  const userId = msg.from.id;
  const chatId = msg.chat.id;
  if (!isAdmin(userId)) {
    bot.sendMessage(chatId, '❌ غير مصرح لك باستخدام هذا البوت.');
    log(`Unauthorized /start from user ${userId}`);
    return;
  }
  const message = `
مرحبًا! هذه أوامر البوت:
🟢 /stream <fbkey> <m3u8> [watermark] [cc] - بدء بث جديد
📷 /watermark <url> <name> - تحميل شعار جديد
🔁 /urlchange <id> <new_m3u8> - تغيير رابط البث
✍️ /cchange <id> <new_text> - تغيير نص CC المتحرك
🛑 /stop <id> - إيقاف بث
📟 /check - معلومات النظام
`;
  bot.sendMessage(chatId, message.trim());
  log(`Admin ${userId} used /start`);
});

// /watermark <url> <name>
bot.onText(/\/watermark (.+) (.+)/, async (msg, match) => {
  const userId = msg.from.id;
  const chatId = msg.chat.id;
  if (!isAdmin(userId)) {
    bot.sendMessage(chatId, '❌ غير مصرح.');
    log(`Unauthorized /watermark from user ${userId}`);
    return;
  }
  const url = match[1].trim();
  const name = match[2].trim();

  try {
    const filePath = await downloadWatermark(url, name);
    bot.sendMessage(chatId, `✅ تم تحميل الشعار وحفظه باسم: ${name}.png`);
    log(`Watermark downloaded by user ${userId}: ${filePath}`);
  } catch (error) {
    bot.sendMessage(chatId, `❌ خطأ في تحميل الشعار: ${error.message}`);
    log(`Watermark download failed for user ${userId}: ${error.message}`);
  }
});

// /stream <fbkey> <m3u8> [watermark] [cc]
bot.onText(/\/stream (.+?) (.+?)(?: (.+?))?(?: (.+))?/, async (msg, match) => {
  const userId = msg.from.id;
  const chatId = msg.chat.id;
  if (!isAdmin(userId)) {
    bot.sendMessage(chatId, '❌ غير مصرح.');
    log(`Unauthorized /stream from user ${userId}`);
    return;
  }

  const fbKey = match[1].trim();
  const m3u8Url = match[2].trim();
  const watermarkName = match[3] ? match[3].trim() : null;
  const ccText = match[4] ? match[4].trim() : '';

  const rtmpsUrl = `rtmps://live-api-s.facebook.com:443/rtmp/${fbKey}`;
  let watermarkPath = null;

  if (watermarkName) {
    watermarkPath = path.join(WATERMARK_DIR, `${watermarkName}.png`);
    if (!fs.existsSync(watermarkPath)) {
      bot.sendMessage(chatId, `❌ الشعار ${watermarkName}.png غير موجود.`);
      log(`Watermark ${watermarkName}.png not found for user ${userId}`);
      return;
    }
  }

  if (!m3u8Url.startsWith('http') || !rtmpsUrl.startsWith('rtmps')) {
    bot.sendMessage(chatId, '❌ رابط M3U8 أو مفتاح فيسبوك غير صالح.');
    log(`Invalid stream URLs from user ${userId}: ${m3u8Url}, ${rtmpsUrl}`);
    return;
  }

  // Generate unique stream ID
  let streamId;
  do streamId = generateStreamId(); while (activeStreams.has(streamId));

  // Build FFmpeg command
  let cmd = `${ffmpegPath} -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5 -itsoffset 5 -re -i "${m3u8Url}" `;

  if (watermarkPath) {
    cmd += `-i "${watermarkPath}" -filter_complex "[0:v][1:v]overlay=10:10[vt];`;
  } else {
    cmd += `-filter_complex "[0:v]copy[vt];`;
  }

  cmd += `[vt]drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:text='${ccText}':fontcolor=white:fontsize=24:x=w-tw-10*t:y=h-th-10,`;
  cmd += `drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:text='Vanilla X':fontcolor=white:fontsize=16:x=10:y=10:box=1:boxcolor=black@0.5:boxborderw=5[outv]" `;
  cmd += `-map "[outv]" -map 0:a -c:v libx264 -preset veryfast -b:v 3000k -c:a aac -f flv "${rtmpsUrl}"`;

  const proc = spawn(cmd, { shell: true });

  let hasResponded = false;

  // Error detection & early failure message
  proc.stderr.on('data', (data) => {
    const errorText = data.toString();
    log(`FFmpeg stderr (Stream ${streamId}): ${errorText}`);

    if (!hasResponded) {
      if (errorText.includes('Server error') || errorText.includes('Invalid data')) {
        bot.sendMessage(chatId, `❌ فشل بدء البث ${streamId}: مفتاح RTMPS غير صالح.`);
      } else if (errorText.includes('No such file') || errorText.includes('Invalid argument')) {
        bot.sendMessage(chatId, `❌ فشل بدء البث ${streamId}: رابط M3U8 غير صالح.`);
      } else {
        bot.sendMessage(chatId, `❌ فشل بدء البث ${streamId}: خطأ غير معروف.`);
      }
      hasResponded = true;
      proc.kill('SIGTERM');
      activeStreams.delete(streamId);
    }
  });

  // On process spawn, wait 3 sec then confirm success if no errors
  proc.on('spawn', () => {
    setTimeout(() => {
      if (!hasResponded) {
        bot.sendMessage(chatId, `✅ تم بدء البث: ${streamId}`);
        hasResponded = true;
        activeStreams.set(streamId, {
          process: proc,
          chatId,
          rtmpsUrl,
          m3u8Url,
          cc: ccText,
          watermark: watermarkName,
        });
        log(`Stream ${streamId} started for user ${userId}`);
      }
    }, 3000);
  });

  proc.on('close', (code) => {
    log(`FFmpeg closed (Stream ${streamId}) with code ${code}`);
    if (!hasResponded && code !== 0) {
      bot.sendMessage(chatId, `❌ فشل بدء البث ${streamId}: FFmpeg أغلق بالكود ${code}.`);
      activeStreams.delete(streamId);
    } else {
      activeStreams.delete(streamId);
    }
  });

  proc.on('error', (err) => {
    if (!hasResponded) {
      bot.sendMessage(chatId, `❌ خطأ في البث ${streamId}: ${err.message}`);
      hasResponded = true;
    }
    activeStreams.delete(streamId);
    log(`FFmpeg error (Stream ${streamId}): ${err.message}`);
  });
});

// /urlchange <streamId> <new_m3u8>
bot.onText(/\/urlchange (\d{4}) (.+)/, (msg, match) => {
  const userId = msg.from.id;
  const chatId = msg.chat.id;
  if (!isAdmin(userId)) {
    bot.sendMessage(chatId, '❌ غير مصرح.');
    log(`Unauthorized /urlchange from user ${userId}`);
    return;
  }
  const streamId = match[1];
  const newM3u8Url = match[2].trim();

  if (!activeStreams.has(streamId)) {
    bot.sendMessage(chatId, `❌ لا يوجد بث برقم ${streamId}.`);
    return;
  }

  if (!newM3u8Url.startsWith('http')) {
    bot.sendMessage(chatId, '❌ رابط M3U8 جديد غير صالح.');
    return;
  }

  const stream = activeStreams.get(streamId);
  stream.process.kill('SIGTERM');
  bot.sendMessage(chatId, `⏳ جاري تحديث رابط البث ${streamId}...`);

  // Re-run stream with new URL but same other parameters
  bot.emit('text', {
    ...msg,
    text: `/stream ${stream.rtmpsUrl.split('/').pop()} ${newM3u8Url} ${stream.watermark || ''} ${stream.cc}`,
  });
});

// /cchange <streamId> <new_cc>
bot.onText(/\/cchange (\d{4}) (.+)/, (msg, match) => {
  const userId = msg.from.id;
  const chatId = msg.chat.id;
  if (!isAdmin(userId)) {
    bot.sendMessage(chatId, '❌ غير مصرح.');
    log(`Unauthorized /cchange from user ${userId}`);
    return;
  }
  const streamId = match[1];
  const newCcText = match[2].trim();

  if (!activeStreams.has(streamId)) {
    bot.sendMessage(chatId, `❌ لا يوجد بث برقم ${streamId}.`);
    return;
  }

  const stream = activeStreams.get(streamId);
  stream.process.kill('SIGTERM');
  bot.sendMessage(chatId, `⏳ جاري تحديث النص المتحرك للبث ${streamId}...`);

  // Re-run stream with new CC text but same other parameters
  bot.emit('text', {
    ...msg,
    text: `/stream ${stream.rtmpsUrl.split('/').pop()} ${stream.m3u8Url} ${stream.watermark || ''} ${newCcText}`,
  });
});

// /stop <streamId>
bot.onText(/\/stop (\d{4})/, (msg, match) => {
  const userId = msg.from.id;
  const chatId = msg.chat.id;
  const streamId = match[1];
  if (!isAdmin(userId)) {
    bot.sendMessage(chatId, '❌ غير مصرح.');
    log(`Unauthorized /stop from user ${userId}`);
    return;
  }
  if (!activeStreams.has(streamId)) {
    bot.sendMessage(chatId, `❌ لا يوجد بث برقم ${streamId}.`);
    return;
  }

  const stream = activeStreams.get(streamId);
  stream.process.kill('SIGTERM');
  activeStreams.delete(streamId);
  bot.sendMessage(chatId, `🛑 تم إيقاف البث ${streamId}.`);
  log(`Stream ${streamId} stopped by user ${userId}`);
});

// /check system info (RAM in GB, uptime, loadavg)
bot.onText(/\/check/, (msg) => {
  const userId = msg.from.id;
  const chatId = msg.chat.id;
  if (!isAdmin(userId)) {
    bot.sendMessage(chatId, '❌ غير مصرح.');
    log(`Unauthorized /check from user ${userId}`);
    return;
  }

  const mem = process.memoryUsage();
  const totalMem = os.totalmem();
  const freeMem = os.freemem();
  const load = os.loadavg();
  const uptime = process.uptime();

  const usedGB = (mem.rss / 1024 / 1024 / 1024).toFixed(2);
  const freeGB = (freeMem / 1024 / 1024 / 1024).toFixed(2);
  const totalGB = (totalMem / 1024 / 1024 / 1024).toFixed(2);

  bot.sendMessage(chatId, `
📟 معلومات النظام:
- وقت التشغيل: ${(uptime / 60).toFixed(1)} دقيقة
- ذاكرة البوت المستخدمة: ${usedGB} جيجابايت
- الذاكرة الحرة للنظام: ${freeGB} جيجابايت
- إجمالي ذاكرة النظام: ${totalGB} جيجابايت
- معدل تحميل النظام: ${load.map(v => v.toFixed(2)).join(', ')}
`.trim());
  log(`System info sent to user ${userId}`);
});

console.log(`✅ Bot started and polling...`);