# تقرير نظام البث (Broadcast System)

## 📋 نظرة عامة

النظام يحتوي على **نظامين منفصلين للبث**:

1. **البث من بوت التحكم المركزي** (Control Bot Broadcast)
2. **البث من البوتات الفرعية** (Sub Bot Broadcast)

---

## 1️⃣ البث من بوت التحكم المركزي

### الأوامر المتاحة

| الأمر | الوظيفة |
|-------|---------|
| `/broadcast <label> <text>` | بث لمستخدمي بوت معين |
| `/broadcast_all <text>` | بث لجميع المستخدمين في كل البوتات |

---

### `/broadcast <label> <text>` - بث لبوت معين

**الملف**: `control_bot.php` السطر 690

**من يستطيع استخدامه**: المالك فقط

#### كود كامل:

```php
case '/broadcast': {
    // التحقق من المدخلات
    if (count($args) < 2) { 
        tg_send($CONTROL_TOKEN, $chat_id, "الاستخدام: /broadcast <label> <text>"); 
        break; 
    }
    
    // استخراج label والرسالة
    $label = array_shift($args);
    $payload = trim(substr($text, strlen('/broadcast ' . $label)));
    
    // جلب البوت من قاعدة البيانات
    $stmt = $db->prepare("SELECT bot_token FROM sub_bots WHERE label=?");
    $stmt->execute([$label]);
    $bot = $stmt->fetch();
    
    if (!$bot) { 
        tg_send($CONTROL_TOKEN, $chat_id, "لم يتم العثور على البوت."); 
        break; 
    }
    
    $tok = $bot['bot_token'];
    
    // جلب مستخدمي هذا البوت (غير المحظورين)
    $stmt = $db->prepare("SELECT user_id FROM allowed_users WHERE bot_token=? AND banned=0");
    $stmt->execute([$tok]);
    $users = $stmt->fetchAll();
    
    // حلقة الإرسال
    $sent = 0; 
    $fail = 0;
    
    foreach ($users as $u) {
        $r = tg_call($tok, 'sendMessage', [
            'chat_id' => $u['user_id'], 
            'text' => $payload, 
            'parse_mode' => 'HTML', 
            'disable_web_page_preview' => true
        ]);
        
        if (!empty($r['ok'])) {
            $sent++;
        } else {
            $fail++;
        }
        
        usleep(200000); // تأخير 0.2 ثانية
    }
    
    // إرسال النتيجة للمالك
    tg_send($CONTROL_TOKEN, $chat_id, "📢 تم الإرسال إلى مستخدمي <b>{$label}</b>: ✅ {$sent} | ❌ {$fail}");
    break;
}
```

#### مسار التنفيذ:

```
المالك يرسل: /broadcast test1 مرحباً بالجميع
    ↓
1. استخراج label = "test1"
2. استخراج payload = "مرحباً بالجميع"
    ↓
3. جلب bot_token من sub_bots WHERE label='test1'
    ↓
4. جلب المستخدمين:
   SELECT user_id FROM allowed_users 
   WHERE bot_token='{token}' AND banned=0
    ↓
5. foreach المستخدمين:
     - إرسال الرسالة عبر Telegram API
     - تأخير 200ms
     - تسجيل النجاح/الفشل
    ↓
6. إرسال النتيجة للمالك:
   "📢 تم الإرسال: ✅ 50 | ❌ 3"
```

#### الميزات:

✅ بسيط ومباشر
✅ تأخير 200ms لتجنب Rate Limiting
✅ عرض إحصائيات النجاح/الفشل
✅ لا يسجل في قاعدة البيانات

#### العيوب:

⚠️ لا يوجد تسجيل في broadcast_log
⚠️ لا يستخدم `DISTINCT` في الاستعلام (قد يكرر إذا كان المستخدم مسجل أكثر من مرة)

---

### `/broadcast_all <text>` - بث لجميع البوتات

**الملف**: `control_bot.php` السطر 712

**من يستطيع استخدامه**: المالك فقط

#### كود كامل:

```php
case '/broadcast_all': {
    // التحقق من المدخلات
    if (count($args) < 1) { 
        tg_send($CONTROL_TOKEN, $chat_id, "الاستخدام: /broadcast_all <text>"); 
        break; 
    }
    
    // استخراج الرسالة
    $payload = trim(substr($text, strlen('/broadcast_all')));
    
    // جلب جميع البوتات
    $bots = $db->query("SELECT bot_token, label FROM sub_bots")->fetchAll();
    
    $sent = 0; 
    $fail = 0;
    
    // حلقة على البوتات
    foreach ($bots as $b) {
        // جلب مستخدمي هذا البوت
        $stmt = $db->prepare("SELECT user_id FROM allowed_users WHERE bot_token=? AND banned=0");
        $stmt->execute([$b['bot_token']]);
        $users = $stmt->fetchAll();
        
        // حلقة على المستخدمين
        foreach ($users as $u) {
            $r = tg_call($b['bot_token'], 'sendMessage', [
                'chat_id' => $u['user_id'], 
                'text' => $payload, 
                'parse_mode' => 'HTML', 
                'disable_web_page_preview' => true
            ]);
            
            if (!empty($r['ok'])) {
                $sent++;
            } else {
                $fail++;
            }
            
            usleep(200000); // تأخير 0.2 ثانية
        }
    }
    
    // إرسال النتيجة
    tg_send($CONTROL_TOKEN, $chat_id, "📣 تم الإرسال للجميع: ✅ {$sent} | ❌ {$fail}");
    break;
}
```

#### مسار التنفيذ:

```
المالك يرسل: /broadcast_all إعلان مهم للجميع
    ↓
1. جلب جميع البوتات من sub_bots
    ↓
2. foreach بوت:
     ↓
     2a. جلب مستخدمي هذا البوت
     ↓
     2b. foreach مستخدم:
           - إرسال الرسالة
           - تأخير 200ms
    ↓
3. إرسال النتيجة للمالك
```

#### الميزات:

✅ يغطي جميع البوتات دفعة واحدة
✅ تأخير بين كل رسالة

#### العيوب:

⚠️ **بطيء جداً** مع عدد كبير من المستخدمين
⚠️ قد يرسل لنفس المستخدم أكثر من مرة (إذا كان مسجلاً في أكثر من بوت)
⚠️ لا يوجد تسجيل

---

## 2️⃣ البث من البوتات الفرعية

### الأوامر المتاحة

| الأمر | الصلاحية المطلوبة |
|-------|-------------------|
| `/broadcast <text>` | 1 (Broadcast Permission) |
| `/broadcast_media` | 1 (قيد التطوير) |
| `/broadcast_log` | 1 (عرض السجل) |

---

### `/broadcast <text>` - بث من بوت فرعي

**الملف**: `sub_bot.php` السطر 385

**من يستطيع استخدامه**: المدراء الفرعيون بصلاحية (1)

#### كود كامل:

```php
case '/broadcast': {
    // التحقق من الصلاحيات
    if (!has_permission($user_id, 1, $token)) {
        tg_send($token, $chat_id, "⛔️ ليس لديك صلاحية البث");
        exit;
    }
    
    // التحقق من المدخلات
    if (!$arg) {
        tg_send($token, $chat_id, "الاستخدام: /broadcast <نص الرسالة>");
        exit;
    }
    
    // جلب مستخدمي البوت (DISTINCT للتأكد من عدم التكرار)
    $users = $db->prepare("SELECT DISTINCT user_id FROM allowed_users WHERE bot_token=? AND banned=0");
    $users->execute([$token]);
    $list = $users->fetchAll(PDO::FETCH_COLUMN);
    
    // حلقة الإرسال
    $success = 0; 
    $failed = 0;
    
    foreach ($list as $uid) {
        $result = tg_send($token, $uid, $arg);
        
        if ($result) {
            $success++;
        } else {
            $failed++;
        }
        
        usleep(50000); // تأخير 50ms فقط (أسرع)
    }
    
    // تسجيل في broadcast_log
    $db->prepare("INSERT INTO broadcast_log(bot_token, manager_id, message_text, recipients_count) VALUES(?,?,?,?)")
        ->execute([$token, $user_id, substr($arg, 0, 500), $success]);
    
    // إرسال النتيجة
    tg_send($token, $chat_id, "✅ <b>اكتمل البث</b>\n\n✅ نجح: {$success}\n❌ فشل: {$failed}");
    exit;
}
```

#### مسار التنفيذ:

```
المدير يرسل: /broadcast مرحباً بالجميع
    ↓
1. التحقق من الصلاحيات (has_permission)
    ↓ (إذا لا → رفض)
    ↓
2. جلب المستخدمين:
   SELECT DISTINCT user_id FROM allowed_users 
   WHERE bot_token=? AND banned=0
    ↓
3. foreach المستخدمين:
     - إرسال الرسالة عبر tg_send()
     - تأخير 50ms
     - تسجيل النجاح/الفشل
    ↓
4. تسجيل في broadcast_log:
   - bot_token
   - manager_id (معرف المدير)
   - message_text (أول 500 حرف)
   - recipients_count
    ↓
5. إرسال النتيجة للمدير
```

#### الميزات:

✅ يستخدم `DISTINCT` لمنع التكرار
✅ يسجل في `broadcast_log`
✅ أسرع (50ms بدلاً من 200ms)
✅ نظام صلاحيات متكامل

#### العيوب:

⚠️ **مشكلة محتملة**: إذا استخدم `FETCH_COLUMN` بشكل خاطئ قد يحدث تكرار

---

### `/broadcast_log` - سجل البث

**الملف**: `sub_bot.php` السطر 425

#### كود:

```php
case '/broadcast_log': {
    if (!has_permission($user_id, 1, $token)) {
        tg_send($token, $chat_id, "⛔️ ليس لديك صلاحية عرض سجل البث");
        exit;
    }
    
    // جلب آخر 10 عمليات بث
    $logs = $db->prepare("SELECT * FROM broadcast_log WHERE bot_token=? AND manager_id=? ORDER BY id DESC LIMIT 10");
    $logs->execute([$token, $user_id]);
    $list = $logs->fetchAll();
    
    if (!$list) {
        tg_send($token, $chat_id, "لا يوجد سجل بث");
        exit;
    }
    
    $out = "📜 <b>سجل البث:</b>\n\n";
    foreach ($list as $l) {
        $preview = substr($l['message_text'], 0, 50) . '...';
        $out .= "📅 {$l['created_at']}\n";
        $out .= "👥 {$l['recipients_count']} مستلم\n";
        $out .= "📝 {$preview}\n\n";
    }
    
    tg_send($token, $chat_id, $out);
    exit;
}
```

#### الميزة:

✅ المدير يستطيع تتبع عمليات البث التي قام بها

---

## 🔀 مقارنة بين النظامين

| الميزة | بوت التحكم | البوتات الفرعية |
|--------|-----------|-----------------|
| **الأمر** | `/broadcast <label> <text>` | `/broadcast <text>` |
| **من يستطيع** | المالك فقط | المدراء بصلاحية (1) |
| **الاستعلام** | `SELECT user_id FROM allowed_users WHERE bot_token=? AND banned=0` | `SELECT DISTINCT user_id FROM allowed_users WHERE bot_token=? AND banned=0` |
| **استخدام DISTINCT** | ❌ لا | ✅ نعم |
| **التأخير بين الرسائل** | 200ms | 50ms |
| **التسجيل في broadcast_log** | ❌ لا | ✅ نعم |
| **عرض السجل** | ❌ لا | ✅ نعم (`/broadcast_log`) |
| **نظام الصلاحيات** | ❌ لا (مالك فقط) | ✅ نعم (صلاحية 1) |
| **الهدف** | بوت معين أو الكل | البوت الحالي فقط |

---

## ⚠️ المشاكل المعروفة

### 1. مشكلة التكرار المحتمل في بوت التحكم

**الموقع**: `control_bot.php` السطر 699

**الكود**:
```php
$stmt = $db->prepare("SELECT user_id FROM allowed_users WHERE bot_token=? AND banned=0");
```

**المشكلة**: 
- لا يستخدم `DISTINCT`
- إذا كان هناك سجلات مكررة في `allowed_users` (بسبب خطأ في الإدراج)، سيرسل للمستخدم أكثر من مرة

**الحل المقترح**:
```php
$stmt = $db->prepare("SELECT DISTINCT user_id FROM allowed_users WHERE bot_token=? AND banned=0");
```

---

### 2. مشكلة التكرار في `/broadcast_all`

**الموقع**: `control_bot.php` السطر 712-729

**المشكلة**:
- المستخدم المسجل في أكثر من بوت سيتلقى الرسالة أكثر من مرة
- مثال:
  - user123 مسجل في bot1
  - user123 مسجل في bot2
  - `/broadcast_all` سيرسل له مرتين

**الحل المقترح**:
```php
// جلب جميع المستخدمين الفريدين من كل البوتات
$stmt = $db->query("SELECT DISTINCT user_id FROM allowed_users WHERE banned=0");
$users = $stmt->fetchAll();

// إرسال لكل مستخدم مرة واحدة فقط
foreach ($users as $u) {
    // استخدام أي بوت للإرسال (يفضل بوت مركزي)
    $r = tg_call($CONTROL_TOKEN, 'sendMessage', [
        'chat_id' => $u['user_id'], 
        'text' => $payload
    ]);
    // ...
}
```

---

### 3. عدم وجود Queue System

**المشكلة**:
- البث يتم بشكل متزامن (Synchronous)
- إذا كان هناك 1000 مستخدم، ستستغرق العملية:
  - بوت التحكم: 1000 × 200ms = **200 ثانية (3.3 دقيقة)**
  - البوتات الفرعية: 1000 × 50ms = **50 ثانية**
- المستخدم (المالك/المدير) ينتظر حتى انتهاء الكل

**الحل المقترح**:
- استخدام `broadcast_jobs` الموجود في قاعدة البيانات
- إنشاء مهمة بث (job) وتنفيذها في الخلفية (Background)
- إرسال تنبيه عند الانتهاء

---

### 4. Telegram Rate Limiting

**المشكلة**:
- تيليجرام يفرض حدود:
  - 30 رسالة/ثانية لكل بوت
  - 20 رسالة/دقيقة لنفس المستخدم
- مع تأخير 50ms، نرسل **20 رسالة/ثانية** (آمن)
- لكن مع عدد كبير (10,000+ مستخدم)، قد يحدث Block مؤقت

**الحل المقترح**:
- زيادة التأخير إلى 100ms (10 رسائل/ثانية)
- استخدام Batch Processing (تقسيم المستخدمين إلى مجموعات)

---

## 📊 جدول broadcast_log

```sql
CREATE TABLE `broadcast_log` (
  `id` bigint(20) PRIMARY KEY AUTO_INCREMENT,
  `bot_token` varchar(128) NOT NULL,
  `manager_id` bigint(20) NOT NULL,
  `message_text` text NOT NULL,
  `recipients_count` int(11) DEFAULT 0,
  `created_at` timestamp DEFAULT current_timestamp()
);
```

**يُستخدم في**: البوتات الفرعية فقط

**الفائدة**: تتبع من قام بالبث ومتى وكم مستلم

---

## 📊 جدول broadcast_jobs (غير مستخدم حالياً)

```sql
CREATE TABLE `broadcast_jobs` (
  `id` bigint(20) PRIMARY KEY AUTO_INCREMENT,
  `job_key` varchar(64) UNIQUE NOT NULL,
  `initiator_user_id` bigint(20) NOT NULL,
  `target_type` enum('all','bot','group') NOT NULL,
  `target_value` varchar(64) DEFAULT NULL,
  `message_text` text NOT NULL,
  `status` enum('pending','running','completed','failed') DEFAULT 'pending',
  `processed_count` int(11) DEFAULT 0,
  `failed_count` int(11) DEFAULT 0,
  `created_at` timestamp DEFAULT current_timestamp(),
  `completed_at` timestamp NULL
);
```

**الوظيفة المخططة**: تنفيذ البث في الخلفية (لم يتم تفعيلها بعد)

---

## 🎯 ملخص الاختلافات الرئيسية

### منطق الحلقات:

**بوت التحكم**:
```php
foreach ($users as $u) {
    tg_call($bot_token, 'sendMessage', [
        'chat_id' => $u['user_id'],
        'text' => $payload
    ]);
    usleep(200000);
}
```

**البوتات الفرعية**:
```php
foreach ($list as $uid) {
    tg_send($token, $uid, $arg);
    usleep(50000);
}
```

---

### جلب المستخدمين:

**بوت التحكم** (خطر التكرار):
```sql
SELECT user_id FROM allowed_users 
WHERE bot_token=? AND banned=0
```

**البوتات الفرعية** (آمن):
```sql
SELECT DISTINCT user_id FROM allowed_users 
WHERE bot_token=? AND banned=0
```

---

## 💡 سبب التكرار غير المنتهي (محتمل)

**السيناريو المحتمل**:

1. بوت فرعي يرسل broadcast
2. الاستعلام يجلب المستخدمين
3. أثناء الإرسال، يضاف مستخدم جديد
4. إذا كان هناك **خطأ في المنطق** (مثل عدم استخدام Snapshot)، قد يعيد جلب المستخدمين
5. حلقة لا نهائية

**لكن الكود الحالي لا يحتوي على هذه المشكلة** (يجلب القائمة مرة واحدة فقط)

**المشكلة الحقيقية المحتملة**:
- إذا كان `tg_send()` يُرجع خطأ خاطئ (true بدلاً من false)
- قد يعتقد النظام أن الإرسال نجح ويعيد المحاولة

---

## 🔧 توصيات للتحسين

### 1. توحيد المنطق

استخدام نفس الطريقة في كلا النظامين:
```php
SELECT DISTINCT user_id FROM allowed_users 
WHERE bot_token=? AND banned=0
```

### 2. إضافة Queue System

- استخدام `broadcast_jobs`
- تنفيذ في الخلفية عبر Cron أو Worker
- إرسال تنبيه عند الانتهاء

### 3. إضافة Pagination

إذا كان عدد المستخدمين أكثر من 1000:
```php
$limit = 1000;
$offset = 0;
while (true) {
    $users = $db->prepare("SELECT DISTINCT user_id FROM allowed_users WHERE bot_token=? LIMIT ? OFFSET ?");
    $users->execute([$token, $limit, $offset]);
    $batch = $users->fetchAll();
    if (empty($batch)) break;
    
    foreach ($batch as $u) {
        // إرسال
    }
    $offset += $limit;
}
```

### 4. تحسين التسجيل

- تسجيل في `broadcast_log` حتى في بوت التحكم
- إضافة حقل `failed_count`
- حفظ الأخطاء التفصيلية

---

## 🎯 الخلاصة

- ✅ نظام البث في **البوتات الفرعية أفضل** (DISTINCT + تسجيل + صلاحيات)
- ⚠️ نظام البث في **بوت التحكم يحتاج تحسين** (إضافة DISTINCT)
- ⚠️ `/broadcast_all` **يحتاج إعادة تصميم** (لتجنب التكرار للمستخدمين المسجلين في أكثر من بوت)
- 💡 **يُنصح بتفعيل** `broadcast_jobs` للبث الكبير (1000+ مستخدم)
