From 8335db37e05b5bba2bc8d66f008d6ddc3b8e8694 Mon Sep 17 00:00:00 2001 From: HosseinDahaei Date: Wed, 1 Apr 2026 16:28:36 +0330 Subject: [PATCH 1/3] =?UTF-8?q?update=20=E2=9C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + docs.md | 889 +++++++++++++++++++++++++++++++++++++++++++++++ main.py | 56 +++ requirements.txt | 3 +- skyroom.py | 228 ++++++++++-- 5 files changed, 1145 insertions(+), 32 deletions(-) create mode 100644 docs.md create mode 100644 main.py diff --git a/.gitignore b/.gitignore index 19303d9..91641f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ secrets +.env # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/docs.md b/docs.md new file mode 100644 index 0000000..d61d459 --- /dev/null +++ b/docs.md @@ -0,0 +1,889 @@ +# راهنمای استفاده از وب‌سرویس اسکای‌روم + +## آشنایی با وب‌سرویس اسکای‌روم + +### معرفی +وب‌سرویس اسکای‌روم با هدف یک‍پارچه‌سازی و بکارگیری سرویس‌های اسکای‌روم در سایر سامانه‌ها و برنامه‌های نرم‌افزاری طراحی و پیاده‌سازی شده است. با بهره‌گیری از وب‌سرویس اسکای‌روم می‌توانید سرویس‌ها، اتاق‌های مجازی، کاربران و سایر امکانات فراهم شده را از درون سامانه یا اپلیکیشن خود و با فراخوانی دستورات وب‌سرویس (API) مدیریت نمایید. + +### مشخصات فنی +وب‌سرویس اسکای‌روم بر پایه REST طراحی و پیاده‌سازی گردیده و در آن تلاش شده است تا ضمن الگوبرداری از استانداردهای رایج دنیا، سادگی در اولویت نخست باشد. از اینرو در وب‌سرویس اسکای‌روم تمامی درخواست‌ها (request) به شکل **POST** ارسال می‌گردند. + +- فرمت داده‌های ارسالی (Content-Type) می‌تواند یکی از انواع **JSON** یا **URL-Encoded** باشد. +- پاسخ همواره **JSON** خواهد بود. +- به منظور تفکیک خطاهای شبکه و سرور از خطاهای برنامه، تمامی پاسخ‌ها از سوی وب‌سرویس (چه موفق چه ناموفق) با کد وضعیت **200** برگردانده می‌شود. + (`200 = HTTP Status Code`) + +### نکات مهم برای توسعه‌دهندگان +- بهتر است اتاق‌ها و کاربران **یک‌بار ایجاد** و به دفعات استفاده شوند. پس از ایجاد هر اتاق یا کاربر، شناسه آن را در دیتابیس ذخیره کرده و در نشست‌های بعدی استفاده کنید. ساخت اتاق و کاربر برای هر بار برگزاری کلاس و حذف آن‌ها در پایان کلاس (خصوصاً زمانی که تعداد کلاس‌ها زیاد باشد) توصیه نمی‌شود؛ چون علاوه بر ایجاد سربار، ممکن است باعث عبور از محدودیت‌های سرویس‌دهنده و دریافت خطا شود. +- اگر تعداد کلاس‌های همزمان زیادی دارید (مثل مدارس و دانشگاه‌ها)، ایجاد لینک ورود با تابع `createLoginUrl` هزینه زیادی خواهد داشت و ممکن است با محدودیت نرخ ارسال درخواست در دقیقه مواجه شوید. توصیه می‌شود لینک‌های ورود را از پیش ایجاد و در دیتابیس نگهداری کنید تا با هر بار ورود و خروج کاربر نیاز به ساخت دوباره آن نباشد. با پارامتر `ttl` می‌توانید زمان منقضی شدن لینک‌ها را مدیریت نمایید. +- در بروزرسانی‌های اخیر روی توابع `getRoom` و `getUser`، امکان جستجوی اتاق/کاربر با **نام** فراهم شده است؛ بنابراین برای پیدا کردن یک کاربر یا اتاق نیازی به دریافت کل لیست نیست. این بهبود می‌تواند در راندمان و کاهش خطاها بسیار مؤثر باشد. +- نرخ ارسال درخواست‌ها در حال حاضر محدود به **۵۰۰ درخواست در دقیقه** است. درخواست‌های بیشتر با خطای **503** روبرو می‌شوند. +- به علت اجتناب‌ناپذیر بودن اختلالات شبکه، علاوه بر مدیریت خطاهای وب‌سرویس، خطاهای زیرساختی مثل ریسالو نشدن دامنه، تایم‌آوت و خطای SSL را نیز مدیریت کرده و لاگ کنید. + +--- + +## آدرس وب‌سرویس +ساختار آدرس (URL) وب‌سرویس: + +``` +https://www.skyroom.online/skyroom/api/{your-api-key} +``` + +در اینجا `{your-api-key}` یک رشته به طول ۵۰ حرف و کلید اختصاصی شما برای استفاده از وب‌سرویس است و در تمامی درخواست‌ها جزء ثابت آدرس خواهد بود. + +--- + +## ارسال درخواست +شکل کلی درخواست: + +```http +POST https://www.skyroom.online/skyroom/api/apikey-71-819540-0f178abb0c712c4cfd5ae13e4c54687a +``` + +```json +{ + "action": "actionName", + "params": { + "param_1": "value1", + "param_2": "value2", + "param_3": "value3" + } +} +``` + +- `action`: تابعی که قصد فراخوانی آن را داریم. +- `params`: پارامترهای تابع. + +> توجه: نام تابع و پارامترهای ارسالی به کوچکی و بزرگی حروف حساس هستند. + +--- + +## دریافت پاسخ +فرم کلی پاسخ‌ها: + +```json +{ + "ok": true, + "result": "action-result" +} +``` + +- `ok`: مقدار `true` یا `false` (موفق/ناموفق) +- `result`: نتیجه عملیات در صورت موفق بودن (می‌تواند `number`، `string`، `[]` یا `{}` باشد) + +نمونه‌ها: + +```json +{ "ok": true, "result": 12 } +``` + +```json +{ "ok": true, "result": "https://www.skyroom.online/ch/web-conference" } +``` + +```json +{ + "ok": true, + "result": ["item1", "item2", "item3"] +} +``` + +```json +{ + "ok": true, + "result": { + "key1": 32, + "key2": "value2", + "key3": ["item1", "item2", "item3"] + } +} +``` + +--- + +## مدیریت خطاها +تمامی پاسخ‌ها (موفق/ناموفق) با کد وضعیت HTTP برابر **200** برمی‌گردند. بنابراین: + +- اگر HTTP Status Code چیزی غیر از `200` باشد: خطای شبکه/سرور رخ داده و باید مدیریت شود. +- اگر خطای برنامه‌ای رخ دهد، بدنه پاسخ این شکل را دارد: + +```http +HTTP/1.1 200 OK +``` + +```json +{ + "ok": false, + "error_code": 14, + "error_message": "درخواست شما با خطا روبرو شد." +} +``` + +### کد خطاهای عمومی + +| کد خطا | توضیح | +|---:|---| +| 10 | وب‌سرویس غیرفعال است. | +| 11 | کلید وب‌سرویس نامعتبر است. | +| 12 | درخواست شما غیرمجاز است. | +| 13 | درخواست شما معتبر نیست. | +| 14 | درخواست شما با خطا روبرو شد. | +| 15 | داده‌ مورد نظر پیدا نشد. | + +--- + +# امکانات مدیریتی +امکانات مدیریتی وب‌سرویس اسکای‌روم شامل سه بخش است: + +1. مدیریت سرویس‌ها +2. مدیریت اتاق‌ها +3. مدیریت کاربران + +--- + +## ۱) مدیریت سرویس‌ها +شما در اسکای‌روم سرویس خود را بر اساس مولفه‌هایی مانند تعداد کاربر همزمان، تعداد ویدیوی همزمان در هر اتاق، حجم نفرساعت مصرفی و بازه زمانی بهره‌برداری خریداری می‌کنید. برای ساخت اتاق‌های مجازی، وجود حداقل یک سرویس فعال الزامی است. + +- اگر بیش از یک سرویس فعال دارید، هنگام ایجاد اتاق باید `service_id` را مشخص کنید. +- اگر تنها یک سرویس فعال دارید، نیازی به ارسال `service_id` نیست و اتاق‌ها به طور پیش‌فرض روی سرویس فعال ساخته می‌شوند. +- می‌توانید با تابع `updateRoom` سرویس مربوط به اتاق را تغییر دهید. + +### مشخصات سرویس + +| ویژگی | نوع | مقدار | +|---|---|---| +| id | number | شناسه سرویس | +| title | string | عنوان سرویس (حداکثر ۱۲۸ حرف) | +| status | number | وضعیت سرویس (0: غیرفعال - 1: فعال) | +| user_limit | number | سقف تعداد کاربر آنلاین | +| video_limit | number | سقف تعداد ویدیوی همزمان (در هر اتاق) | +| time_limit | number | محدودیت نفرثانیه | +| time_usage | number | نفرثانیه مصرف شده | +| start_time | Unix time | زمان شروع سرویس | +| stop_time | Unix time | زمان پایان سرویس | +| create_time | Unix time | زمان ایجاد سرویس | +| update_time | Unix time | زمان آخرین بروزرسانی | + +### توابع سرویس + +| نام تابع | شرح | مقدار بازگشتی | نوع مقدار بازگشتی | +|---|---|---|---| +| getServices | دریافت لیست سرویس‌های موجود | لیست سرویس‌ها | [] | + +#### دریافت لیست سرویس‌های موجود + +**درخواست:** +```json +{ + "action": "getServices" +} +``` + +**پاسخ (نمونه):** +```json +[ + { + "id": 1, + "title": "سرویس وب کنفرانس", + "status": 1, + "user_limit": 10, + "video_limit": 8, + "time_limit": null, + "time_usage": 2213762, + "start_time": 1582851460, + "stop_time": 1580259460, + "create_time": 1582851460, + "update_time": 1580259460 + }, + { + "id": 2, + "title": "سرویس آموزش آنلاین", + "status": 0, + "user_limit": 100, + "video_limit": 3, + "time_limit": null, + "time_usage": 21124, + "start_time": 1580259460, + "stop_time": 1582851460, + "create_time": 1479021195, + "update_time": 1582851460 + } +] +``` + +--- + +## ۲) مدیریت اتاق‌ها +با استفاده از اتاق‌های مجازی می‌توان رویدادهای مختلف را به صورت همزمان و کاملاً مجزا برگزار نمود. هر اتاق دارای یک نام واحد است که آدرس (URL) اتاق نیز با همان نام ساخته می‌شود؛ بنابراین بهتر است نام را به صورت لاتین وارد کنید. + +می‌توان محدودیت‌هایی مثل سقف کاربر آنلاین، میزان نفرساعت مصرفی و جلوگیری از ورود کاربران پیش از ورود اپراتور را اعمال کرد. + +### مشخصات اتاق + +| ویژگی | نوع | مقدار | +|---|---|---| +| id | number | شناسه اتاق | +| service_id | number | شناسه سرویس | +| name | string | نام اتاق به لاتین (حداکثر ۱۲۸ حرف) | +| title | string | عنوان اتاق (حداکثر ۱۲۸ حرف) | +| description | string | شرح اتاق (حداکثر ۵۱۲ حرف) | +| status | number | وضعیت اتاق | +| guest_login | bool | ورود به صورت میهمان | +| guest_limit | number | محدودیت تعداد میهمان (0 = نامحدود) | +| op_login_first | bool | ابتدا اپراتور وارد شود | +| max_users | number | سقف تعداد کاربر آنلاین | +| session_duration | number | محدودیت طول نشست | +| time_limit | number | محدودیت نفرثانیه | +| time_usage | number | نفرثانیه مصرف شده | +| time_total | number | مجموع نفرثانیه مصرف شده | +| create_time | Unix time | زمان ایجاد | +| update_time | Unix time | آخرین بروزرسانی | + +### توابع اتاق + +| نام تابع | شرح | مقدار بازگشتی | نوع مقدار بازگشتی | +|---|---|---|---| +| getRooms | دریافت لیست اتاق‌های موجود | لیست اتاق‌ها | [] | +| countRooms | دریافت تعداد اتاق‌های موجود | تعداد اتاق‌ها | number | +| getRoom | دریافت مشخصات یک اتاق | مشخصات اتاق | {} | +| getRoomUrl | دریافت آدرس یک اتاق | آدرس اتاق | string | +| createRoom | ایجاد اتاق جدید | شناسه اتاق ایجاد شده | number | +| updateRoom | بروزرسانی اتاق | 1 | number | +| deleteRoom | حذف اتاق | 1 | number | +| getRoomUsers | دریافت لیست کاربران دارای دسترسی به اتاق | لیست کاربران | [] | +| addRoomUsers | افزودن دسترسی کاربران به اتاق | تعداد کاربران افزوده شده | number | +| removeRoomUsers | حذف دسترسی کاربران از اتاق | تعداد کاربران حذف شده | number | +| updateRoomUser | تغییر دسترسی کاربر به اتاق | 1 | number | + +#### دریافت لیست اتاق‌های موجود + +**درخواست:** +```json +{ + "action": "getRooms" +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": [ + { + "id": 1, + "service_id": 8230, + "name": "meeting", + "title": "اتاق جلسات", + "status": 1 + }, + { + "id": 2, + "service_id": 8230, + "name": "learning-php", + "title": "کلاس آموزش PHP", + "status": 0 + } + ] +} +``` + +#### دریافت تعداد اتاق‌های موجود + +**درخواست:** +```json +{ + "action": "countRooms" +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": 2 +} +``` + +#### دریافت مشخصات یک اتاق با استفاده از شناسه + +**درخواست:** +```json +{ + "action": "getRoom", + "params": { + "room_id": 1 + } +} +``` + +#### دریافت مشخصات یک اتاق با استفاده از نام + +**درخواست:** +```json +{ + "action": "getRoom", + "params": { + "name": "meeting" + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": { + "id": 1, + "service_id": 8230, + "name": "meeting", + "title": "اتاق جلسات", + "description": null, + "status": 1, + "guest_login": false, + "guest_limit": 0, + "op_login_first": true, + "max_users": 8, + "session_duration": null, + "time_limit": null, + "time_usage": 184486, + "time_total": 3144460, + "create_time": 1479021434, + "update_time": 1501559112 + } +} +``` + +#### دریافت آدرس یک اتاق + +**درخواست:** +```json +{ + "action": "getRoomUrl", + "params": { + "room_id": 1, + "language": "fa" + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": "https://www.skyroom.online/ch/faramooz/meeting" +} +``` + +#### ایجاد اتاق جدید + +**درخواست:** +```json +{ + "action": "createRoom", + "params": { + "name": "math-exercise", + "title": "کلاس حل تمرین ریاضی", + "guest_login": false, + "op_login_first": true, + "max_users": 10 + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": 3 +} +``` + +#### بروزرسانی اتاق + +**درخواست:** +```json +{ + "action": "updateRoom", + "params": { + "room_id": 3, + "max_users": 20 + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": 1 +} +``` + +#### حذف اتاق + +**درخواست:** +```json +{ + "action": "deleteRoom", + "params": { + "room_id": 3 + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": 1 +} +``` + +#### دریافت لیست کاربران دارای دسترسی به یک اتاق + +**درخواست:** +```json +{ + "action": "getRoomUsers", + "params": { + "room_id": 2 + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": [ + { + "user_id": 6344, + "username": "operator", + "nickname": "اپراتور", + "access": 3 + }, + { + "user_id": 6345, + "username": "presenter", + "nickname": "ارایه کننده", + "access": 2 + }, + { + "user_id": 6347, + "username": "user-150", + "nickname": "کاربر عادی", + "access": 1 + } + ] +} +``` + +> انواع دسترسی‌ها: «کاربر عادی»، «ارایه کننده»، «اپراتور» و «مدیر». جدول انواع دسترسی در انتهای همین صفحه آمده است. + +#### افزودن دسترسی کاربران به یک اتاق + +**درخواست:** +```json +{ + "action": "addRoomUsers", + "params": { + "room_id": 1, + "users": [ + { "user_id": 6344 }, + { "user_id": 6345, "access": 2 } + ] + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": 2 +} +``` + +#### حذف دسترسی کاربران از یک اتاق + +**درخواست:** +```json +{ + "action": "removeRoomUsers", + "params": { + "room_id": 1, + "users": [6344, 6345] + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": 2 +} +``` + +#### تغییر دسترسی کاربر به یک اتاق + +**درخواست:** +```json +{ + "action": "updateRoomUser", + "params": { + "room_id": 1, + "user_id": 6344, + "access": 3 + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": 1 +} +``` + +--- + +## ۳) مدیریت کاربران +برای ورود به اتاق‌های مجازی و شرکت در رویدادها نیاز به ساخت حساب کاربری است. برای ساخت حساب کاربری، وارد کردن **نام کاربری**، **گذرواژه** و **نام نمایشی** الزامی است. + +- با هر حساب کاربری تنها یک بار می‌توان وارد اتاق شد. +- اگر قرار است چند نفر به صورت همزمان از یک حساب استفاده کنند، باید یک حساب **عمومی** (Public) ایجاد کنید. +- پس از ساخت حساب کاربری باید دسترسی کاربر به اتاق/اتاق‌ها اعطا شود. + +### مشخصات کاربر + +| ویژگی | نوع | مقدار | +|---|---|---| +| id | number | شناسه کاربر | +| username | string | نام کاربری به لاتین (حداکثر ۳۲ حرف) | +| nickname | string | نام نمایشی (حداکثر ۱۲۸ حرف) | +| password | string | گذرواژه (حداکثر ۲۴ حرف) | +| email | string | ایمیل (حداکثر ۱۲۸ حرف) | +| fname | string | نام (حداکثر ۱۲۸ حرف) | +| lname | string | نام خانوادگی (حداکثر ۱۲۸ حرف) | +| gender | number | جنسیت | +| status | number | وضعیت کاربر | +| is_public | bool | کاربر عمومی | +| concurrent | number | محدودیت استفاده همزمان (0 = نامحدود) | +| time_limit | number | محدودیت زمانی (ثانیه) | +| time_usage | number | زمان مصرف شده (ثانیه) | +| time_total | number | مجموع زمان مصرف شده (ثانیه) | +| expiry_date | Unix time | تاریخ انقضا | +| create_time | Unix time | زمان ایجاد | +| update_time | Unix time | آخرین بروزرسانی | + +### توابع کاربر + +| نام تابع | شرح | مقدار بازگشتی | نوع مقدار بازگشتی | +|---|---|---|---| +| getUsers | دریافت لیست کاربران موجود | لیست کاربران | [] | +| countUsers | دریافت تعداد کاربران موجود | تعداد کاربران | number | +| getUser | دریافت مشخصات یک کاربر | مشخصات کاربر | {} | +| createUser | ایجاد کاربر جدید | شناسه کاربر ایجاد شده | number | +| updateUser | بروزرسانی کاربر | 1 | number | +| deleteUser | حذف کاربر | 1 | number | +| deleteUsers | حذف چند کاربر | 1 | number | +| getUserRooms | دریافت لیست اتاق‌های کاربر | لیست اتاق‌ها | [] | +| addUserRooms | افزودن به اتاق‌های کاربر | 1 | number | +| removeUserRooms | حذف از اتاق‌های کاربر | 1 | number | +| createLoginUrl | دریافت آدرس ورود مستقیم به اتاق بدون نیاز به لاگین و ساخت کاربر | آدرس (URL) | string | + +#### دریافت لیست کاربران موجود + +**درخواست:** +```json +{ + "action": "getUsers" +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": [ + { "id": 1, "username": "parham", "nickname": "پرهام", "status": 1 }, + { "id": 2, "username": "sara", "nickname": "سارا", "status": 1 } + ] +} +``` + +#### دریافت مشخصات یک کاربر با استفاده از شناسه + +**درخواست:** +```json +{ + "action": "getUser", + "params": { + "user_id": 1 + } +} +``` + +#### دریافت مشخصات یک کاربر با استفاده از نام کاربری + +**درخواست:** +```json +{ + "action": "getUser", + "params": { + "username": "parham" + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": { + "id": 1, + "username": "parham", + "nickname": "پرهام", + "email": "example@gmail.com", + "fname": "پرهام", + "lname": "عابدینی", + "gender": 1, + "status": 1, + "is_public": false, + "concurrent": 0, + "time_limit": null, + "time_usage": 426466, + "time_total": 8464516, + "expiry_date": null, + "create_time": 1489021434, + "update_time": 1489021434 + } +} +``` + +#### ایجاد کاربر جدید + +**درخواست:** +```json +{ + "action": "createUser", + "params": { + "username": "test-user", + "password": "12345", + "nickname": "کاربر عمومی", + "status": 1, + "is_public": true + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": 3 +} +``` + +#### بروزرسانی کاربر + +**درخواست:** +```json +{ + "action": "updateUser", + "params": { + "user_id": 3, + "password": "tu2017!" + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": 1 +} +``` + +#### حذف کاربر + +**درخواست:** +```json +{ + "action": "deleteUser", + "params": { + "user_id": 3 + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": 1 +} +``` + +#### حذف چند کاربر + +**درخواست:** +```json +{ + "action": "deleteUsers", + "params": { + "users": [3, 4, 5] + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": { + "success": 2, + "failure": 1 + } +} +``` + +#### دریافت لیست اتاق‌های یک کاربر + +**درخواست:** +```json +{ + "action": "getUserRooms", + "params": { + "user_id": 1 + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": [ + { "room_id": 1, "name": "meeting", "title": "اتاق جلسات", "access": 1 }, + { "room_id": 2, "name": "learning-php", "title": "کلاس آموزش PHP", "access": 2 } + ] +} +``` + +#### افزودن به اتاق‌های یک کاربر + +**درخواست:** +```json +{ + "action": "addUserRooms", + "params": { + "user_id": 2, + "rooms": [ + { "room_id": 1 }, + { "room_id": 2, "access": 2 } + ] + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": 2 +} +``` + +#### حذف از اتاق‌های یک کاربر + +**درخواست:** +```json +{ + "action": "removeUserRooms", + "params": { + "user_id": 2, + "rooms": [1, 2] + } +} +``` + +**پاسخ:** +```json +{ + "ok": true, + "result": 2 +} +``` + +### دریافت آدرس ورود مستقیم به اتاق بدون نیاز به لاگین و ساخت کاربر (`createLoginUrl`) +با این تابع می‌توان برای یک اتاق لینک ورود مستقیم ایجاد کرد تا کاربر بدون نیاز به درج نام کاربری و گذرواژه وارد اتاق شود. در این روش نیازی به ساخت کاربر و اعطای دسترسی نیست و کافی است شناسه اتاق و مشخصات لازم ارسال شود. + +**درخواست:** +```json +{ + "action": "createLoginUrl", + "params": { + "room_id": 1, + "user_id": "sina", + "nickname": "Sina", + "access": 3, + "concurrent": 1, + "language": "en", + "ttl": 3600 + } +} +``` + +- `room_id`: شناسه اتاقی که می‌خواهید برای آن لینک ورود بسازید. +- `user_id` (**ضروری**): عدد یا رشته (بدون فاصله). برای جلوگیری از ورود همزمان چند کاربر با لینک تولید شده. +- `concurrent`: تعداد کاربرانی که می‌توانند همزمان با این لینک وارد اتاق شوند. اگر `user_id` خالی باشد، این پارامتر بی‌تأثیر است. مقدار پیش‌فرض `1`. +- `ttl` (Time To Live): مدت اعتبار لینک به ثانیه. پس از آن لینک نامعتبر می‌شود. مقدار پیش‌فرض `3600` (یک ساعت). + +**پاسخ:** +```json +{ + "ok": true, + "result": "https://www.skyroom.online/ch/faramooz/meeting/t/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0eXBlIjoiYXBpX2xvZ2luIiwiZXhwIjoxNjA1MDg2NTY2LCJyb29tX2lkIjoxLCJ1aWQiOiJAYXBpXC9ta2giLCJuaWNrbmFtZSI6Ik1vaHNlbiIsInJvbGUiOjMsImNvbmN1cnJlbnQiOjUsImdlbmRlciI6MH0.hGJyJvnDFSDMEXCt_q1zUru8INLcyH3UGH7kh4D2SAc/l/en" +} +``` + +--- + +# کدهای مفید + +## وضعیت سرویس (`status`) +| کد | شرح | +|---:|---| +| 0 | غیرفعال | +| 1 | فعال | + +## وضعیت اتاق (`status`) +| کد | شرح | +|---:|---| +| 0 | غیرفعال | +| 1 | فعال | + +## وضعیت کاربر (`status`) +| کد | شرح | +|---:|---| +| 0 | غیرفعال | +| 1 | فعال | + +## جنسیت کاربر (`gender`) +| کد | شرح | +|---:|---| +| 0 | نامعلوم | +| 1 | مرد | +| 2 | زن | + +## دسترسی به اتاق (`access`) +| کد | شرح | +|---:|---| +| 1 | کاربر عادی | +| 2 | ارایه کننده | +| 3 | اپراتور | +``` diff --git a/main.py b/main.py new file mode 100644 index 0000000..6ab0a54 --- /dev/null +++ b/main.py @@ -0,0 +1,56 @@ +from skyroom import * +from dotenv import load_dotenv +import os + +load_dotenv() + +# Load API key from .env file +api_key = os.getenv('SKYROOM_API_KEY') +if not api_key: + raise ValueError('SKYROOM_API_KEY not set in .env file') + +api = SkyroomAPI(api_key) + +try: + # Example: Get all rooms + print("=== Getting Rooms ===") + rooms = api.getRooms() + print(f"Found {len(rooms)} rooms") + + # Example: Get services (new method) + print("\n=== Getting Services ===") + services = api.getServices() + print(f"Found {len(services)} services") + + # Example: Get room by ID (new parameter hints) + if rooms: + room_id = rooms[0]['id'] + print(f"\n=== Getting Room {room_id} ===") + room = api.getRoom(room_id=room_id) + print(f"Room: {room['name']} - {room['title']}") + + # Example: Create login URL (new method with full params) + # Note: This would create a real URL if room_id exists + print("\n=== Create Login URL Example ===") + print("Usage: api.createLoginUrl(room_id=123, user_id='user123', nickname='Test User', access=1)") + print("This creates a direct login link for a room without requiring user creation") + + # Example: Delete multiple users (new method) + print("\n=== Delete Users Example ===") + print("Usage: api.deleteUsers(users=[user_id1, user_id2, user_id3])") + print("Returns: {'success': count, 'failure': count}") + + # Example: Create room (new parameter hints) + print("\n=== Create Room Example ===") + print("Usage: api.createRoom(name='test-room', title='Test Room', max_users=10)") + + # Example: Update user (new parameter hints) + print("\n=== Update User Example ===") + print("Usage: api.updateUser(user_id=123, nickname='New Name', status=1)") + +except APIException as e: + print(f"API Error: {e}") +except HTTPException as e: + print(f"HTTP Error: {e}") +except Exception as e: + print(f"Unexpected Error: {e}") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e000c3a..e4162bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -requests>=2.22.0 \ No newline at end of file +requests>=2.22.0 +python-dotenv \ No newline at end of file diff --git a/skyroom.py b/skyroom.py index 8a7b3d6..fa800e1 100644 --- a/skyroom.py +++ b/skyroom.py @@ -54,71 +54,237 @@ def _request(self, action, params=None): # 1.Service Management - # to-be-implemented by Skyroom (hopefully) :) + def getServices(self, params=None): + return self._request('getServices', params) # 2.Rooms Management - def getRooms(self, params=None): - return self._request('getRooms', params) - - def countRooms(self, params=None): - return self._request('countRooms', params) - - def getRoom(self, params=None): + def getRooms(self): + """Get list of rooms.""" + return self._request('getRooms') + + def countRooms(self): + """Get room count.""" + return self._request('countRooms') + + def getRoom(self, room_id=None, name=None, params=None): + """Get room by ID or name.""" + if params is None: + params = {} + if room_id is not None: + params['room_id'] = room_id + if name is not None: + params['name'] = name return self._request('getRoom', params) - def getRoomUrl(self, params=None): + def getRoomUrl(self, room_id=None, language=None, params=None): + """Get room URL by room_id and optional language.""" + if params is None: + params = {} + if room_id is not None: + params['room_id'] = room_id + if language is not None: + params['language'] = language return self._request('getRoomUrl', params) - def createRoom(self, params=None): + def createRoom(self, name, title, guest_login=False, op_login_first=True, max_users=0, guest_limit=0, + status=1, service_id=None, description=None, session_duration=None, time_limit=None, params=None): + """Create a new room.""" + if params is None: + params = { + 'name': name, + 'title': title, + 'guest_login': guest_login, + 'op_login_first': op_login_first, + 'max_users': max_users, + 'guest_limit': guest_limit, + 'status': status, + } + if service_id is not None: + params['service_id'] = service_id + if description is not None: + params['description'] = description + if session_duration is not None: + params['session_duration'] = session_duration + if time_limit is not None: + params['time_limit'] = time_limit return self._request('createRoom', params) - def updateRoom(self, params=None): + def updateRoom(self, room_id, title=None, description=None, status=None, guest_login=None, + guest_limit=None, op_login_first=None, max_users=None, session_duration=None, + time_limit=None, service_id=None, params=None): + """Update room attributes.""" + if params is None: + params = {'room_id': room_id} + for key, value in { + 'title': title, + 'description': description, + 'status': status, + 'guest_login': guest_login, + 'guest_limit': guest_limit, + 'op_login_first': op_login_first, + 'max_users': max_users, + 'session_duration': session_duration, + 'time_limit': time_limit, + 'service_id': service_id, + }.items(): + if value is not None: + params[key] = value return self._request('updateRoom', params) - def deleteRoom(self, params=None): + def deleteRoom(self, room_id=None, params=None): + """Delete room by ID.""" + if params is None: + params = {'room_id': room_id} return self._request('deleteRoom', params) - def getRoomUsers(self, params=None): + def getRoomUsers(self, room_id=None, params=None): + """List users in room.""" + if params is None: + params = {'room_id': room_id} return self._request('getRoomUsers', params) - def addRoomUsers(self, params=None): + def addRoomUsers(self, room_id=None, users=None, params=None): + """Add users to room. + + users: list of dicts [{'user_id': ..., 'access': ...}] or user IDs as numbers + """ + if params is None: + params = {'room_id': room_id, 'users': users} return self._request('addRoomUsers', params) - def removeRoomUsers(self, params=None): + def removeRoomUsers(self, room_id=None, users=None, params=None): + """Remove user access from room.""" + if params is None: + params = {'room_id': room_id, 'users': users} return self._request('removeRoomUsers', params) - def updateRoomUser(self, params=None): + def updateRoomUser(self, room_id, user_id=None, access=None, params=None): + """Change a user's room access.""" + if params is None: + params = {'room_id': room_id} + if user_id is not None: + params['user_id'] = user_id + if access is not None: + params['access'] = access return self._request('updateRoomUser', params) # 3. Users Management - def getUsers(self, params=None): - return self._request('getUsers', params) - - def countUsers(self, params=None): - return self._request('countUsers', params) - - def getUser(self, params=None): + def getUsers(self): + """List all users.""" + return self._request('getUsers') + + def countUsers(self): + """Count users.""" + return self._request('countUsers') + + def getUser(self, user_id=None, username=None, params=None): + """Get user by ID or username.""" + if params is None: + params = {} + if user_id is not None: + params['user_id'] = user_id + if username is not None: + params['username'] = username return self._request('getUser', params) - def createUser(self, params=None): + def createUser(self, username, password, nickname, status=1, is_public=False, + email=None, fname=None, lname=None, gender=None, concurrent=None, + time_limit=None, expiry_date=None, params=None): + """Create a user.""" + if params is None: + params = { + 'username': username, + 'password': password, + 'nickname': nickname, + 'status': status, + 'is_public': is_public, + } + if email is not None: + params['email'] = email + if fname is not None: + params['fname'] = fname + if lname is not None: + params['lname'] = lname + if gender is not None: + params['gender'] = gender + if concurrent is not None: + params['concurrent'] = concurrent + if time_limit is not None: + params['time_limit'] = time_limit + if expiry_date is not None: + params['expiry_date'] = expiry_date return self._request('createUser', params) - def updateUser(self, params=None): + def updateUser(self, user_id, password=None, nickname=None, email=None, fname=None, + lname=None, gender=None, status=None, is_public=None, concurrent=None, + time_limit=None, expiry_date=None, params=None): + """Update user details.""" + if params is None: + params = {'user_id': user_id} + for key, value in { + 'password': password, + 'nickname': nickname, + 'email': email, + 'fname': fname, + 'lname': lname, + 'gender': gender, + 'status': status, + 'is_public': is_public, + 'concurrent': concurrent, + 'time_limit': time_limit, + 'expiry_date': expiry_date, + }.items(): + if value is not None: + params[key] = value return self._request('updateUser', params) - def deleteUser(self, params=None): + def deleteUser(self, user_id=None, params=None): + """Delete a single user by ID.""" + if params is None: + params = {'user_id': user_id} return self._request('deleteUser', params) - def getUserRooms(self, params=None): + def getUserRooms(self, user_id=None, params=None): + """Get rooms assigned to a user.""" + if params is None: + params = {'user_id': user_id} return self._request('getUserRooms', params) - def addUserRooms(self, params=None): + def addUserRooms(self, user_id=None, rooms=None, params=None): + """Add room access for user.""" + if params is None: + params = {'user_id': user_id, 'rooms': rooms} return self._request('addUserRooms', params) - def removeUserRooms(self, params=None): + def removeUserRooms(self, user_id=None, rooms=None, params=None): + """Remove room access from user.""" + if params is None: + params = {'user_id': user_id, 'rooms': rooms} return self._request('removeUserRooms', params) - def getLoginUrl(self, params=None): - return self._request('getLoginUrl', params) + def deleteUsers(self, users=None, params=None): + """Delete multiple users by IDs.""" + if params is None: + params = {'users': users} + return self._request('deleteUsers', params) + + def createLoginUrl(self, room_id, user_id, nickname, access=1, concurrent=1, + language='fa', ttl=3600, params=None): + """Create direct login URL (room login token).""" + if params is None: + params = { + 'room_id': room_id, + 'user_id': user_id, + 'nickname': nickname, + 'access': access, + 'concurrent': concurrent, + 'language': language, + 'ttl': ttl, + } + return self._request('createLoginUrl', params) + + def getLoginUrl(self, *args, **kwargs): + """Alias of createLoginUrl for backward compatibility.""" + return self.createLoginUrl(*args, **kwargs) From 001d30355af301b34c7e2ce0fd11e3ef0d94af5b Mon Sep 17 00:00:00 2001 From: HosseinDahaei Date: Sun, 5 Apr 2026 12:09:07 +0330 Subject: [PATCH 2/3] =?UTF-8?q?session=20management=20added=20=E2=9C=85=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skyroom.py | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/skyroom.py b/skyroom.py index fa800e1..dfc9478 100644 --- a/skyroom.py +++ b/skyroom.py @@ -37,6 +37,7 @@ def _request(self, action, params=None): if params: data['params'] = params try: + content_data = None content_data = requests.post(url, headers=self.headers, auth=None, json=data, **self.request_kwargs).content try: response = json.loads(content_data.decode("utf-8")) @@ -49,8 +50,9 @@ def _request(self, action, params=None): except ValueError as e: raise HTTPException(e) return response - except requests.exceptions.RequestException as e: - raise HTTPException(e) + except Exception as e: + print("Raw content_data: ",content_data) + raise Exception(e) # 1.Service Management @@ -288,3 +290,44 @@ def createLoginUrl(self, room_id, user_id, nickname, access=1, concurrent=1, def getLoginUrl(self, *args, **kwargs): """Alias of createLoginUrl for backward compatibility.""" return self.createLoginUrl(*args, **kwargs) + + + # Session management + def getSessions(self,room_id): + params = {"room_id":room_id} + return self._request('getSessions', params) + + def getSessionsCount(self,room_id): + params = {"room_id":room_id} + return self._request('getSessionsCount', params) + + def getSession(self,session_id): + params = {"session_id":session_id} + return self._request('getSession', params) + + def getConnections(self,session_id): + params = {'session_id': session_id} + return self._request('getConnections', params) + + def getConnectionsCount(self,session_id): + params = {'session_id': session_id} + return self._request('getConnectionsCount', params) + + def getAttendances(self,session_id): + cons = self.getConnections(session_id) + session_info = self.getSession(session_id) + session_duration = int(session_info['duration']) + output = [] + for item in cons: + user = {} + user['id'] = item['id'] + user['nickname'] = item['nickname'] + user['client_id'] = item['client_id'] + user['start_time'] = item['start_time'] + user['stop_time'] = item['stop_time'] + user['status'] = item['status'] + user['api_user_id'] = item['api_user_id'] + user['att_duration_secs'] = item['duration'] + user['abs_duration_secs'] =str(max(0,session_duration - int(item['duration']))) + output.append(user) + return output \ No newline at end of file From 4857529429976cb9ffee79f958f7f42a29b99806 Mon Sep 17 00:00:00 2001 From: HosseinDahaei Date: Sun, 5 Apr 2026 12:42:16 +0330 Subject: [PATCH 3/3] =?UTF-8?q?getAttendanceOfRoom=20added=20=E2=9C=85=20:?= =?UTF-8?q?rocket:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skyroom.py | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/skyroom.py b/skyroom.py index dfc9478..35b1f0f 100644 --- a/skyroom.py +++ b/skyroom.py @@ -1,6 +1,6 @@ import json - import requests +from collections import defaultdict @@ -330,4 +330,79 @@ def getAttendances(self,session_id): user['att_duration_secs'] = item['duration'] user['abs_duration_secs'] =str(max(0,session_duration - int(item['duration']))) output.append(user) - return output \ No newline at end of file + return output + + + def getAttendanceOfRoom(self, room_id): + sessions = self.getSessions(room_id) + # Will hold the original per-session data + sessions_output = [] + + # For merged view keyed by nickname + merged_by_nickname = defaultdict(lambda: { + 'nickname': None, + 'total_att_duration_secs': 0, + 'total_abs_duration_secs': 0, + 'first_start_time': None, + 'last_stop_time': None, + 'sessions': [] # per-session records for this nickname + }) + + for session in sessions: + session_id = session['id'] + attendances = self.getAttendances(int(session_id)) + + # store per-session raw data + sessions_output.append({ + 'session_id': session_id, + 'attendances': attendances + }) + + # merge by nickname + for att in attendances: + nickname = att.get('nickname') or 'UNKNOWN' + record = merged_by_nickname[nickname] + + record['nickname'] = nickname + # Make sure we treat these as ints + att_duration = int(att.get('att_duration_secs', 0)) + abs_duration = int(att.get('abs_duration_secs', 0)) + + record['total_att_duration_secs'] += att_duration + record['total_abs_duration_secs'] += abs_duration + + # times + start_time = att.get('start_time') + stop_time = att.get('stop_time') + + # Update first_start_time + if start_time is not None: + if record['first_start_time'] is None or start_time < record['first_start_time']: + record['first_start_time'] = start_time + + # Update last_stop_time + if stop_time is not None: + if record['last_stop_time'] is None or stop_time > record['last_stop_time']: + record['last_stop_time'] = stop_time + + # Optionally keep useful fields from each session + record['sessions'].append({ + 'session_id': session_id, + 'user_id': att['id'], + 'client_id': att['client_id'], + 'start_time': att['start_time'], + 'stop_time': att['stop_time'], + 'status': att['status'], + 'api_user_id': att['api_user_id'], + 'att_duration_secs': att_duration, + 'abs_duration_secs': abs_duration + }) + + # Convert defaultdict to a list for clean output + merged_list = list(merged_by_nickname.values()) + + return { + 'room_id': room_id, + 'sessions': sessions_output, + 'merged_by_nickname': merged_list + }