0Pricing
AI Agents · درس

بناء bot لإشعارات الفريق

الرسائل المجدولة، وملخصات الرسائل الخاصة، ووكلاء تنبيهات القنوات

بناء bot لإشعارات الفريق درس مجاني في AI Agents على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Agents، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Agents 4 دروس في المجموع.

بنية روبوت إشعارات الفريق

يراقب روبوت إشعارات الفريق الأنظمة الخارجية — عمليات النشر، وخطوط CI/CD، وتنبيهات المراقبة، وتتبع الأخطاء — وينشر تحديثات منسقة في قنوات Slack ذات الصلة. النمط الأساسي هو: حدث خارجي → webhook → وكيل → رسالة Slack. ويتولى الوكيل التوجيه والتنسيق والتسليم.

# Team Notification Bot Flow:
#
# External System (GitHub, PagerDuty, Sentry, etc.)
#    |
#    | HTTP POST (webhook)
#    v
# Flask/FastAPI webhook endpoint
#    |
#    | Parse event
#    v
# Agent: classify, format, route
#    |
#    | Slack API
#    v
# Team channel / DM / thread

print('Webhook -> Agent -> Slack is the core notification pattern')

استقبال Webhooks خارجية

ترسل الخدمات الخارجية الأحداث إلى روبوتكم عبر Webhooks HTTP. أعدّوا نقطة نهاية Flask تستقبل طلبات POST، وتتحقق من صحتها (بفحص التوقيع إذا كانت الخدمة تدعم ذلك)، ثم تمرّر الحمولة إلى معالج الإشعارات.

from flask import Flask, request, jsonify
import hmac
import hashlib
import os

flask_app = Flask(__name__)

@flask_app.route('/webhook/github', methods=['POST'])
def github_webhook():
    # Verify GitHub signature
    signature = request.headers.get('X-Hub-Signature-256', '')
    secret = os.environ['GITHUB_WEBHOOK_SECRET'].encode()
    body = request.get_data()
    expected = 'sha256=' + hmac.new(secret, body, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(signature, expected):
        return jsonify({'error': 'Invalid signature'}), 403

    event_type = request.headers.get('X-GitHub-Event', '')
    payload = request.json

    handle_github_event(event_type, payload)
    return jsonify({'status': 'ok'}), 200

توجيه الأحداث إلى القناة المناسبة

ينبغي توجيه أنواع الأحداث المختلفة إلى قنوات مختلفة. عرّفوا خريطة توجيه: تذهب أحداث CI/CD إلى #deployments، والأخطاء إلى #alerts، ومراجعات PR إلى #engineering. خزّنوا معرّفات القنوات في متغيرات البيئة لتتمكنوا من تهيئتها دون تغيير الشيفرة.

import os

# Channel routing configuration
CHANNEL_MAP = {
    'deploy': os.environ.get('DEPLOY_CHANNEL', 'C0DEPLOY123'),
    'error': os.environ.get('ERROR_CHANNEL', 'C0ERROR456'),
    'pr_review': os.environ.get('PR_CHANNEL', 'C0PR789'),
    'general': os.environ.get('GENERAL_CHANNEL', 'C0GENERAL'),
}

def route_github_event(event_type, payload):
    if event_type == 'push':
        branch = payload.get('ref', '').replace('refs/heads/', '')
        if branch in ('main', 'master'):
            return CHANNEL_MAP['deploy']
        return CHANNEL_MAP['general']
    elif event_type == 'pull_request':
        return CHANNEL_MAP['pr_review']
    elif event_type == 'workflow_run':
        if payload.get('workflow_run', {}).get('conclusion') == 'failure':
            return CHANNEL_MAP['error']
        return CHANNEL_MAP['deploy']
    return CHANNEL_MAP['general']

# --- demo ---
push_payload = {'ref': 'refs/heads/main'}
pr_payload = {}
wf_payload = {'workflow_run': {'conclusion': 'failure'}}

print('push to main       ->', route_github_event('push', push_payload))
print('pull_request        ->', route_github_event('pull_request', pr_payload))
print('failed workflow_run ->', route_github_event('workflow_run', wf_payload))

تنسيق إشعارات النشر

ينبغي أن توضّح إشعارات النشر: ما الذي نُشر، ومن نشره، وإلى أي بيئة، وما إذا نجح أو فشل. استخدموا أقسام Block Kit وكتل context لإنشاء تنسيق واضح وسهل المسح البصري.

def build_deploy_blocks(repo, branch, commit_sha, deployer, status, env):
    status_emoji = ':white_check_mark:' if status == 'success' else ':x:'
    status_text = 'Success' if status == 'success' else 'Failed'

    blocks = [
        {
            'type': 'header',
            'text': {
                'type': 'plain_text',
                'text': f'{status_emoji} Deploy {status_text}: {repo}'
            }
        },
        {
            'type': 'section',
            'fields': [
                {'type': 'mrkdwn', 'text': f'*Repo:*\n{repo}'},
                {'type': 'mrkdwn', 'text': f'*Environment:*\n{env}'},
                {'type': 'mrkdwn', 'text': f'*Branch:*\n{branch}'},
                {'type': 'mrkdwn', 'text': f'*Deployed by:*\n{deployer}'},
                {'type': 'mrkdwn', 'text': f'*Commit:*\n`{commit_sha[:8]}`'}
            ]
        }
    ]
    return blocks

# --- demo ---
blocks = build_deploy_blocks('coddy-agents', 'main', 'a1b2c3d4e5f6', 'alice', 'success', 'production')
for b in blocks:
    print(b)

إرسال ملخصات مباشرة إلى الأفراد

من الأفضل إرسال بعض الإشعارات في رسالة مباشرة إلى الشخص المعني بدلًا من بثها في قناة. استخدموا client.conversations_open(users=[user_id]) لفتح قناة رسالة مباشرة، ثم انشروا الرسالة في معرّف القناة المُعاد.

def send_dm(client, user_id, text, blocks=None):
    # Open DM channel with the user
    dm_result = client.conversations_open(users=[user_id])
    dm_channel = dm_result['channel']['id']

    # Post message to the DM channel
    msg = {'channel': dm_channel, 'text': text}
    if blocks:
        msg['blocks'] = blocks

    return client.chat_postMessage(**msg)

# Example: DM a developer when their PR build fails
def notify_pr_author_of_failure(client, pr_author_slack_id, pr_title, build_url):
    blocks = [
        {
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': f'Your PR build failed: *{pr_title}*\n<{build_url}|View build logs>'
            }
        }
    ]
    send_dm(client, pr_author_slack_id, f'Build failed: {pr_title}', blocks)

# --- demo: minimal stand-in for the Slack client ---
class _FakeClient:
    def conversations_open(self, users):
        return {'channel': {'id': f'D_{users[0]}'}}
    def chat_postMessage(self, **kwargs):
        print(f"[slack DM] to {kwargs['channel']}: {kwargs['text']}")
        return {'ts': '1700000000.000200'}

notify_pr_author_of_failure(_FakeClient(), 'U_ALICE', 'Add retry logic to fetcher', 'https://ci.example.com/build/42')

تسليم الرسائل وفق جدول زمني

استخدموا APScheduler لإرسال التقارير المجدولة — مثل الملخصات اليومية، أو الموجزات الأسبوعية، أو الإحاطات الصباحية يوم الاثنين. جدْولوا المهام في أوقات محددة باستخدام تعبيرات بأسلوب cron. يعمل المجدول في خيط خلفي إلى جانب معالج الأحداث.

from apscheduler.schedulers.background import BackgroundScheduler
from slack_sdk import WebClient
import os

client = WebClient(token=os.environ['SLACK_BOT_TOKEN'])

def send_daily_summary():
    stats = fetch_daily_stats()  # query your data source
    blocks = [
        {
            'type': 'header',
            'text': {'type': 'plain_text', 'text': 'Daily Team Summary'}
        },
        {
            'type': 'section',
            'text': {'type': 'mrkdwn',
                     'text': f'*PRs merged:* {stats["prs"]}\n'
                             f'*Deploys:* {stats["deploys"]}\n'
                             f'*Incidents:* {stats["incidents"]}'}
        }
    ]
    client.chat_postMessage(
        channel=os.environ['DAILY_CHANNEL'],
        text='Daily Team Summary',
        blocks=blocks
    )

scheduler = BackgroundScheduler()
scheduler.add_job(send_daily_summary, 'cron', hour=9, minute=0)
scheduler.start()
print('Scheduler started: daily summary at 9:00 AM')

تجميع التنبيهات — تجنب إرهاق الإشعارات

يؤدي إرسال رسالة Slack لكل خطأ على حدة إلى إرهاق الإشعارات بسرعة. اجمعوا التنبيهات: اجمعوا الأخطاء خلال فترة زمنية (مثل 5 دقائق)، ثم انشروا رسالة ملخص واحدة. استخدموا عدّادًا وفرّغوا محتواه وفق جدول زمني.

import threading
import time
from collections import defaultdict

class AlertAggregator:
    def __init__(self, client, channel, flush_interval=300):
        self.client = client
        self.channel = channel
        self.flush_interval = flush_interval
        self.buffer = defaultdict(int)  # error_type -> count
        self.lock = threading.Lock()
        self._start_flusher()

    def add_alert(self, error_type):
        with self.lock:
            self.buffer[error_type] += 1

    def _flush(self):
        with self.lock:
            if not self.buffer:
                return
            lines = [f'• {err}: {count}x' for err, count in self.buffer.items()]
            self.buffer.clear()

        self.client.chat_postMessage(
            channel=self.channel,
            text=f'Alert summary ({len(lines)} error types):\n' + '\n'.join(lines)
        )

    def _start_flusher(self):
        def loop():
            while True:
                time.sleep(self.flush_interval)
                self._flush()
        threading.Thread(target=loop, daemon=True).start()

# --- demo (flush immediately instead of waiting flush_interval seconds) ---
class _FakeClient:
    def chat_postMessage(self, **kwargs):
        print(f"[slack] postMessage to {kwargs['channel']}: {kwargs['text']}")

agg = AlertAggregator(_FakeClient(), '#alerts', flush_interval=9999)
agg.add_alert('TimeoutError')
agg.add_alert('TimeoutError')
agg.add_alert('ConnectionError')
agg._flush()  # normally the background thread does this every flush_interval seconds

تنسيق كتل التنبيهات الغنية

ينبغي أن تنقل التنبيهات مستوى الخطورة بسرعة. استخدموا سياقًا مميزًا بالألوان والرموز التعبيرية والحقول المنظمة. أضيفوا زر إجراء يربط بدليل التشغيل أو لوحة التنبيهات حتى يتمكن مهندسو المناوبة من التصرف فورًا من Slack.

def build_incident_alert_blocks(service, error_rate, threshold,
                                 runbook_url, pagerduty_url):
    blocks = [
        {
            'type': 'header',
            'text': {'type': 'plain_text', 'text': ':rotating_light: Incident Alert'}
        },
        {
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': (
                    f'*Service:* `{service}`\n'
                    f'*Error Rate:* {error_rate:.1f}% (threshold: {threshold}%)\n'
                    f'*Status:* Investigating'
                )
            }
        },
        {
            'type': 'actions',
            'elements': [
                {
                    'type': 'button',
                    'text': {'type': 'plain_text', 'text': 'View Runbook'},
                    'url': runbook_url,
                    'action_id': 'view_runbook'
                },
                {
                    'type': 'button',
                    'text': {'type': 'plain_text', 'text': 'PagerDuty'},
                    'url': pagerduty_url,
                    'style': 'danger',
                    'action_id': 'view_pagerduty'
                }
            ]
        }
    ]
    return blocks

# --- demo ---
blocks = build_incident_alert_blocks('checkout-api', 12.4, 5.0,
                                      'https://runbooks.example.com/checkout-api',
                                      'https://pagerduty.example.com/incidents/1')
for b in blocks:
    print(b)

الإشارة إلى فريق المناوبة

ينبغي أن تشير التنبيهات الحرجة إلى مهندس المناوبة بالاسم. استخدموا إشارات مجموعات المستخدمين في Slack (<!subteam^SUBTEAM_ID>) لمجموعات المناوبة، أو ابحثوا عن مستخدم المناوبة من PagerDuty/OpsGenie وأشيروا إليه مباشرةً باستخدام <@USER_ID>.

import requests
import os

def get_oncall_slack_user():
    # Query PagerDuty for current on-call
    headers = {'Authorization': f'Token token={os.environ["PAGERDUTY_TOKEN"]}'}
    r = requests.get(
        'https://api.pagerduty.com/oncalls?include[]=users&limit=1',
        headers=headers
    )
    oncalls = r.json().get('oncalls', [])
    if not oncalls:
        return None
    email = oncalls[0]['user']['email']
    return email

def send_oncall_alert(client, channel, alert_text):
    oncall_email = get_oncall_slack_user()

    if oncall_email:
        # Look up Slack user by email
        user_result = client.users_lookupByEmail(email=oncall_email)
        user_id = user_result['user']['id']
        mention = f'<@{user_id}>'
    else:
        mention = '<!channel>'

    client.chat_postMessage(
        channel=channel,
        text=f'{mention} - CRITICAL ALERT: {alert_text}'
    )

تجميع التنبيهات ذات الصلة في سلاسل محادثات

عندما ترتبط تنبيهات متعددة بالحادث نفسه، انشروها كـردود ضمن سلسلة محادثات على رسالة التنبيه الأصلية. يحافظ ذلك على نظافة القناة الرئيسية، مع الاحتفاظ بسجل التنبيهات الكامل في السلسلة. خزّنوا الطابع الزمني للرسالة الأصلية لإضافة المتابعات ضمن السلسلة.

class IncidentThread:
    def __init__(self, client, channel):
        self.client = client
        self.channel = channel
        self.active_incidents = {}  # service_name -> thread_ts

    def open_incident(self, service, initial_text, blocks=None):
        msg = self.client.chat_postMessage(
            channel=self.channel,
            text=initial_text,
            blocks=blocks
        )
        self.active_incidents[service] = msg['ts']
        return msg['ts']

    def update_incident(self, service, update_text):
        thread_ts = self.active_incidents.get(service)
        if thread_ts:
            self.client.chat_postMessage(
                channel=self.channel,
                thread_ts=thread_ts,
                text=update_text
            )
        else:
            self.open_incident(service, f'[New] {update_text}')

    def close_incident(self, service, resolution_text):
        thread_ts = self.active_incidents.pop(service, None)
        if thread_ts:
            self.client.chat_postMessage(
                channel=self.channel,
                thread_ts=thread_ts,
                text=f':white_check_mark: RESOLVED: {resolution_text}'
            )

# --- demo: minimal stand-in for the Slack client ---
class _FakeClient:
    def __init__(self):
        self._counter = 0
    def chat_postMessage(self, **kwargs):
        self._counter += 1
        ts = f'ts_{self._counter}'
        print(f"[slack] {kwargs.get('text')} (thread_ts={kwargs.get('thread_ts')})")
        return {'ts': ts}

thread = IncidentThread(_FakeClient(), '#incidents')
thread.open_incident('checkout-api', 'Checkout API error rate spiking')
thread.update_incident('checkout-api', 'Rolled back the last deploy')
thread.close_incident('checkout-api', 'Error rate back to normal')

اختبار روبوت الإشعارات

اختبروا روبوت الإشعارات قبل نشره، وذلك بإرسال Webhooks تجريبية باستخدام requests.post() والتحقق من ظهور الرسائل في قناة #bot-testing. اكتبوا نصًا برمجيًا للاختبار يحاكي كل نوع من الأحداث ويتحقق من تنسيق المخرجات.

import requests
import json

def test_webhook(webhook_url, event_type, payload):
    response = requests.post(
        webhook_url,
        json=payload,
        headers={'X-GitHub-Event': event_type, 'Content-Type': 'application/json'}
    )
    print(f'Webhook test {event_type}: {response.status_code}')
    return response

# Test a deploy notification
test_webhook(
    webhook_url='http://localhost:3000/webhook/github',
    event_type='push',
    payload={
        'ref': 'refs/heads/main',
        'pusher': {'name': 'alice'},
        'repository': {'full_name': 'myorg/myapp'},
        'head_commit': {'id': 'abc12345', 'message': 'Fix: auth bug'}
    }
)

print('Check #bot-testing channel for the notification')

تحقق سريع: الرسائل المؤقتة مقابل رسائل القنوات

اختبر مدى فهمكم لتوجيه الإشعارات.

خلاصة بوت إشعارات الفريق

يمكنكم الآن إنشاء بوت متكامل لإشعارات الفريق:

  • نقطة نهاية Webhook: استقبال الأحداث من الأنظمة الخارجية والتحقق منها (التحقق من التوقيعات)
  • توجيه القنوات: ربط أنواع الأحداث بقنوات Slack المناسبة عبر قاموس إعدادات
  • تنبيهات Block Kit: رأس منسق + حقول + أزرار إجراءات تتضمن روابط إلى دليل التشغيل
  • تسليم الرسائل المباشرة: استخدام conversations_open(users=[id]) ثم chat_postMessage لإرسال الرسالة إلى قناة الرسائل المباشرة
  • التقارير المجدولة: استخدام APScheduler مع تعبيرات cron لإعداد ملخصات يومية أو أسبوعية
  • تجميع التنبيهات: تخزين الأخطاء خلال فترة زمنية ثم إرسالها في ملخص واحد
  • إدارة سلاسل المحادثات: وضع تنبيهات المتابعة في سلسلة محادثة للحفاظ على نظافة القناة الرئيسية

الأسئلة الشائعة

هل درس «بناء bot لإشعارات الفريق» مجاني؟

نعم — نص درس «بناء bot لإشعارات الفريق» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Agents، انتقل إلى CoddyKit PRO. تتضمن دورة AI Agents 4 دروس في المجموع.

ماذا ستتعلم في «بناء bot لإشعارات الفريق»؟

الرسائل المجدولة، وملخصات الرسائل الخاصة، ووكلاء تنبيهات القنوات تتمرن على AI Agents مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ AI Agents؟

لا تُشترط خبرة سابقة. AI Agents على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.

كم من الوقت يستغرق درس «بناء bot لإشعارات الفريق»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس AI Agents هذا؟

نعم. كل درس في AI Agents يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. أساسيات Slack Bolt SDK
  2. الاستماع إلى الأحداث وأوامر الشرطة المائلة
  3. إرسال الرسائل والكتل المنسقة
  4. بناء bot لإشعارات الفريق
← العودة إلى AI Agents