ربط مجلس بمنصتك

عندك منصة تعليمية أو نظام فيه محاضرات ومقابلات؟ افتح اجتماعات مجلس من عندك، واعرضها جوه صفحاتك، واختار التسجيل يكون يدوي أو أوتوماتيك، وخد نسخة من التسجيل وقائمة الحضور لنظامك.

المفتاح بيطلع من فريق مجلس لحد دلوقتي: كلّمنا وقولنا اسم منصتك وموقعها. الاجتماعات بتتحسب على باقة الحساب اللي التطبيق متربط بيه (المدة وعدد الأشخاص ودقايق التسجيل).

إزاي بيشتغل

  1. سيرفرك بيعمل اجتماع للمحاضرة بالمفتاح (مرة واحدة، ولو بعتها تاني بنفس externalId بيرجعلك نفس الاجتماع).
  2. لما مدرّس أو طالب يدوس «ادخل»، سيرفرك بيطلب له رابط دخول باسمه ودوره. الرابط بيشتغل مرة واحدة ولمدة قصيرة.
  3. صفحتك بتعرض الرابط جوه iframe بالسكربت بتاعنا، أو تفتحه في تاب جديد.
  4. بيوصلك على رابط الأحداث: الاجتماع بدأ وخلص، مين دخل ومين خرج (بالـ id بتاعه عندك)، والتسجيل لما يجهز.
المفتاح سرّي ومكانه السيرفر بتاعك بس. أي طلب للـ API بيتعمل من الباك إند، مش من صفحة بتتفتح في المتصفح.

المفتاح

كل الطلبات على http://localhost:5370/api/v1، والمفتاح في الهيدر:

Authorization: Bearer mk_xxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

أو X-Api-Key: mk_…. المفتاح الغلط أو الموقوف بيرجع 401.

اعمل اجتماع

curl -X POST http://localhost:5370/api/v1/meetings \
  -H "Authorization: Bearer $MAJLIS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "lecture-1042",
    "title": "رياضيات — الوحدة التالتة",
    "startsAt": "2026-11-02T17:00:00Z",
    "durationMinutes": 90,
    "waitingRoom": true,
    "muteOnEntry": true,
    "recording": "auto"
  }'

الرد:

{
  "id": "kdw-mpqz-tra",
  "externalId": "lecture-1042",
  "title": "رياضيات — الوحدة التالتة",
  "status": "scheduled",          // scheduled | live | ended
  "recording": "auto",            // off | manual | auto
  "participantsNow": 0,
  ...
}

id هو اللي بتستخدمه في باقي الطلبات. كل الحقول اختيارية؛ recording لو مش مبعوت بياخد الافتراضي بتاع تطبيقك. تقدر تعدّل بـ PATCH، وتمسح بـ DELETE، وتنهي اجتماع شغّال للكل بـ POST …/end.

روابط الدخول

رابط لكل شخص، بيتعمل لحظة ما يدوس «ادخل»:

curl -X POST http://localhost:5370/api/v1/meetings/kdw-mpqz-tra/join-links \
  -H "Authorization: Bearer $MAJLIS_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "externalUserId": "student-381", "name": "سارة أحمد", "role": "participant" }'

{ "url": "http://localhost:5370/j/3f1c…", "expiresAt": "2026-11-02T17:05:00Z" }
  • role: host للمدرّس (يتحكم في الاجتماع ويدخّل اللي في الانتظار) أو participant.
  • الرابط بيشتغل مرة واحدة، ولمدة ttlSeconds (من 30 ثانية لحد ساعة، الافتراضي 5 دقايق) لحد ما يتفتح.
  • الاسم بيظهر في الاجتماع زي ما بعته، والشخص مش محتاج حساب في مجلس.
  • externalUserId بيرجعلك في الحضور وفي الأحداث، علشان تربط كل حاجة بالطالب عندك.

اعرضه في صفحتك

ضيف موقعك (زي https://academy.example) في «المواقع المسموح لها» بتاعة التطبيق، وبعدين في الصفحة:

<div id="lecture"></div>
<script src="http://localhost:5370/js/majlis-embed.js"></script>
<script>
  // joinUrl: the "url" your server got from join-links, rendered into the page.
  const meeting = Majlis.embed({ url: joinUrl, container: '#lecture', height: 640 });

  meeting.on('joined', e => console.log('in as', e.displayName));
  meeting.on('recording-started', () => showBadge('بيتسجّل'));
  meeting.on('ended', () => location.href = '/lectures');
  meeting.on('kicked', e => alert(e.reason));

  // meeting.leave();  meeting.toggleMic();  meeting.toggleCamera();  meeting.destroy();
</script>

الأحداث في الصفحة: ready، waiting، joined، participant-joined، participant-left، recording-started، recording-stopped، left، ended، kicked، error، و* لكلهم. جوه الـ iframe مفيش لوجو مجلس ولا رابط دعوة ولا خروج لصفحات مجلس. موقع مش في القايمة بيتمنع يعرض الاجتماع.

من غير سكربت؟ اعرض {url}?embed=true في iframe فيه allow="camera; microphone; display-capture; fullscreen; autoplay"، أو افتح الرابط زي ما هو في تاب جديد.

التسجيل ونسخته

offمفيش تسجيل، ومحدش يقدر يبدأه.
manualالمدرّس يبدأ ويوقف التسجيل من جوه الاجتماع.
autoالتسجيل بيبدأ لوحده أول ما أول شخص يدخل، ومحدش يقدر يوقفه. لو وقع بيتعاد مرة لوحده.

لما التسجيل يجهز بيوصلك حدث recording.ready فيه downloadUrl (شغّال 24 ساعة). نزّل النسخة لسيرفرك، وبعدين قولنا إنها وصلتك:

curl -X POST http://localhost:5370/api/v1/recordings/{recordingId}/received -H "Authorization: Bearer $MAJLIS_KEY"

لو تطبيقك مظبوط على «امسح بعد ما المنصة تاخد نسختها»، التسجيل بيتمسح من عندنا ساعتها. وتقدر تمسحه بنفسك بـ DELETE /api/v1/recordings/{recordingId}، أو تجيب روابط جديدة في أي وقت بـ GET …/meetings/{id}/recordings. التسجيلات بتفضل محفوظة طول ما الباقة شغّالة، وشهر بعد ما تخلص.

الأحداث (Webhooks)

لو حطيت «رابط الأحداث» للتطبيق، بيوصله POST بالشكل ده:

Majlis-Event: participant.joined
Majlis-Delivery: 6c0f…            // same id if it is sent again: use it to ignore repeats
Majlis-Signature: t=1793638800,v1=5d41…

{
  "id": "6c0f…",
  "event": "participant.joined",
  "createdAt": "2026-11-02T17:01:12Z",
  "meeting": { "id": "kdw-mpqz-tra", "externalId": "lecture-1042" },
  "data": { "externalUserId": "student-381", "name": "سارة أحمد", "joinedAt": "…", "sessionId": "…" }
}
الحدثفي data
meeting.startedsessionId, startedAt
meeting.endedsessionId, startedAt, endedAt, peakParticipants
participant.joinedexternalUserId, name, joinedAt, sessionId
participant.leftexternalUserId, name, joinedAt, leftAt, minutes, sessionId
recording.started / ready / failedrecordingId, status, durationSeconds, sizeBytes, downloadUrl, error

رد بأي 2xx خلال 10 ثواني. لو سيرفرك واقع بنعيد بعد 30 ثانية، ودقيقتين، و10، و30، وساعة، وبعدين أطول، لحد يوم كامل. وتأكد من التوقيع قبل ما تصدّق الحدث: HMAC-SHA256 لـ {t}.{body} بـ «سر توقيع الأحداث» بتاع تطبيقك، وارفض اللي t بتاعه أقدم من 5 دقايق.

C#

static bool IsFromMajlis(string body, string header, string secret)
{
    var parts = header.Split(',').Select(p => p.Split('=', 2)).ToDictionary(p => p[0], p => p[1]);
    var t = long.Parse(parts["t"]);
    if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > 300) return false;

    var expected = Convert.ToHexString(HMACSHA256.HashData(
        Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes($"{t}.{body}"))).ToLowerInvariant();
    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(parts["v1"]));
}

Node.js

const crypto = require('crypto');

function isFromMajlis(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

استخدم الـ body زي ما وصل بالظبط (raw)، مش بعد ما تقراه JSON وترجعه نص.

الحضور

curl http://localhost:5370/api/v1/meetings/kdw-mpqz-tra/attendance -H "Authorization: Bearer $MAJLIS_KEY"

[
  { "externalUserId": "student-381", "name": "سارة أحمد", "isGuest": true,
    "joinedAt": "…", "leftAt": "…", "minutes": 52, "sessionId": "…" }
]

كل دخول سطر لوحده: لو الطالب خرج ورجع هتلاقي له سطرين.

كل العناوين

POST/api/v1/meetings
GET/api/v1/meetings/{id}
PATCH/api/v1/meetings/{id}
DELETE/api/v1/meetings/{id}
POST/api/v1/meetings/{id}/end
POST/api/v1/meetings/{id}/join-links
GET/api/v1/meetings/{id}/attendance
GET/api/v1/meetings/{id}/recordings
POST/api/v1/recordings/{recordingId}/received
DELETE/api/v1/recordings/{recordingId}

الأخطاء

أي رفض بيرجع { "error": "…" } فيه السبب (بالإنجليزي، للمطوّر)، مع الكود:

401المفتاح غلط أو التطبيق موقوف.
404الاجتماع أو التسجيل مش موجود، أو تبع تطبيق تاني.
409الاجتماع خلص، أو التسجيل لسه ما جهزش.
422قيمة مش مقبولة (زي recording أو role غلط).