mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
* Switch platform ui * Update room settings based on platform * Add local and none recording options to daily * Don't create tokens for unauthentikated users * Enable knocking for private rooms * Create new meeting on room settings change * Always use 2-200 option for daily * Show recording start trigger for daily * Fix broken test
205 lines
7.0 KiB
Python
205 lines
7.0 KiB
Python
from datetime import datetime
|
|
|
|
from reflector.dailyco_api import (
|
|
CreateMeetingTokenRequest,
|
|
CreateRoomRequest,
|
|
DailyApiClient,
|
|
MeetingParticipantsResponse,
|
|
MeetingTokenProperties,
|
|
RecordingResponse,
|
|
RecordingsBucketConfig,
|
|
RoomPresenceResponse,
|
|
RoomProperties,
|
|
verify_webhook_signature,
|
|
)
|
|
from reflector.db.daily_participant_sessions import (
|
|
daily_participant_sessions_controller,
|
|
)
|
|
from reflector.db.rooms import Room
|
|
from reflector.logger import logger
|
|
from reflector.storage import get_dailyco_storage
|
|
|
|
from ..dailyco_api.responses import RecordingStatus
|
|
from ..schemas.platform import Platform
|
|
from ..utils.daily import DailyRoomName
|
|
from ..utils.string import NonEmptyString
|
|
from .base import ROOM_PREFIX_SEPARATOR, VideoPlatformClient
|
|
from .models import MeetingData, RecordingType, SessionData, VideoPlatformConfig
|
|
|
|
|
|
class DailyClient(VideoPlatformClient):
|
|
PLATFORM_NAME: Platform = "daily"
|
|
TIMESTAMP_FORMAT = "%Y%m%d%H%M%S"
|
|
RECORDING_NONE: RecordingType = "none"
|
|
RECORDING_LOCAL: RecordingType = "local"
|
|
RECORDING_CLOUD: RecordingType = "cloud"
|
|
|
|
def __init__(self, config: VideoPlatformConfig):
|
|
super().__init__(config)
|
|
self._api_client = DailyApiClient(
|
|
api_key=config.api_key,
|
|
webhook_secret=config.webhook_secret,
|
|
timeout=10.0,
|
|
)
|
|
|
|
async def create_meeting(
|
|
self, room_name_prefix: NonEmptyString, end_date: datetime, room: Room
|
|
) -> MeetingData:
|
|
"""
|
|
Daily.co rooms vs meetings:
|
|
- We create a NEW Daily.co room for each Reflector meeting
|
|
- Daily.co meeting/session starts automatically when first participant joins
|
|
- Room auto-deletes after exp time
|
|
- Meeting.room_name stores the timestamped Daily.co room name
|
|
"""
|
|
timestamp = datetime.now().strftime(self.TIMESTAMP_FORMAT)
|
|
room_name = f"{room_name_prefix}{ROOM_PREFIX_SEPARATOR}{timestamp}"
|
|
|
|
enable_recording = None
|
|
if room.recording_type == self.RECORDING_LOCAL:
|
|
enable_recording = "local"
|
|
elif room.recording_type == self.RECORDING_CLOUD:
|
|
enable_recording = "raw-tracks"
|
|
|
|
properties = RoomProperties(
|
|
enable_recording=enable_recording,
|
|
enable_chat=True,
|
|
enable_screenshare=True,
|
|
enable_knocking=room.is_locked,
|
|
start_video_off=False,
|
|
start_audio_off=False,
|
|
exp=int(end_date.timestamp()),
|
|
)
|
|
|
|
if room.recording_type == self.RECORDING_CLOUD:
|
|
daily_storage = get_dailyco_storage()
|
|
assert daily_storage.bucket_name, "S3 bucket must be configured"
|
|
properties.recordings_bucket = RecordingsBucketConfig(
|
|
bucket_name=daily_storage.bucket_name,
|
|
bucket_region=daily_storage.region,
|
|
assume_role_arn=daily_storage.role_credential,
|
|
allow_api_access=True,
|
|
)
|
|
|
|
request = CreateRoomRequest(
|
|
name=room_name,
|
|
privacy="private" if room.is_locked else "public",
|
|
properties=properties,
|
|
)
|
|
|
|
result = await self._api_client.create_room(request)
|
|
|
|
return MeetingData(
|
|
meeting_id=result.id,
|
|
room_name=result.name,
|
|
room_url=result.url,
|
|
host_room_url=result.url,
|
|
platform=self.PLATFORM_NAME,
|
|
extra_data=result.model_dump(),
|
|
)
|
|
|
|
async def get_room_sessions(self, room_name: str) -> list[SessionData]:
|
|
"""Get room session history from database (webhook-stored sessions).
|
|
|
|
Daily.co doesn't provide historical session API, so we query our database
|
|
where participant.joined/left webhooks are stored.
|
|
"""
|
|
from reflector.db.meetings import meetings_controller # noqa: PLC0415
|
|
|
|
meeting = await meetings_controller.get_by_room_name(room_name)
|
|
if not meeting:
|
|
return []
|
|
|
|
sessions = await daily_participant_sessions_controller.get_by_meeting(
|
|
meeting.id
|
|
)
|
|
|
|
return [
|
|
SessionData(
|
|
session_id=s.id,
|
|
started_at=s.joined_at,
|
|
ended_at=s.left_at,
|
|
)
|
|
for s in sessions
|
|
]
|
|
|
|
async def get_room_presence(self, room_name: str) -> RoomPresenceResponse:
|
|
"""Get room presence/session data for a Daily.co room."""
|
|
return await self._api_client.get_room_presence(room_name)
|
|
|
|
async def get_meeting_participants(
|
|
self, meeting_id: str
|
|
) -> MeetingParticipantsResponse:
|
|
"""Get participant data for a specific Daily.co meeting."""
|
|
return await self._api_client.get_meeting_participants(meeting_id)
|
|
|
|
async def get_recording(self, recording_id: str) -> RecordingResponse:
|
|
return await self._api_client.get_recording(recording_id)
|
|
|
|
async def list_recordings(
|
|
self,
|
|
room_name: NonEmptyString | None = None,
|
|
starting_after: str | None = None,
|
|
ending_before: str | None = None,
|
|
limit: int = 100,
|
|
) -> list[RecordingResponse]:
|
|
return await self._api_client.list_recordings(
|
|
room_name=room_name,
|
|
starting_after=starting_after,
|
|
ending_before=ending_before,
|
|
limit=limit,
|
|
)
|
|
|
|
async def get_recording_status(
|
|
self, recording_id: NonEmptyString
|
|
) -> RecordingStatus:
|
|
recording = await self.get_recording(recording_id)
|
|
return recording.status
|
|
|
|
async def upload_logo(self, room_name: str, logo_path: str) -> bool:
|
|
return True
|
|
|
|
def verify_webhook_signature(
|
|
self, body: bytes, signature: str, timestamp: str | None = None
|
|
) -> bool:
|
|
"""Verify Daily.co webhook signature using dailyco_api module."""
|
|
if not self.config.webhook_secret:
|
|
logger.warning("Webhook secret not configured")
|
|
return False
|
|
|
|
return verify_webhook_signature(
|
|
body=body,
|
|
signature=signature,
|
|
timestamp=timestamp or "",
|
|
webhook_secret=self.config.webhook_secret,
|
|
)
|
|
|
|
async def create_meeting_token(
|
|
self,
|
|
room_name: DailyRoomName,
|
|
start_cloud_recording: bool,
|
|
enable_recording_ui: bool,
|
|
user_id: NonEmptyString | None = None,
|
|
is_owner: bool = False,
|
|
) -> NonEmptyString:
|
|
properties = MeetingTokenProperties(
|
|
room_name=room_name,
|
|
user_id=user_id,
|
|
start_cloud_recording=start_cloud_recording,
|
|
enable_recording_ui=enable_recording_ui,
|
|
is_owner=is_owner,
|
|
)
|
|
request = CreateMeetingTokenRequest(properties=properties)
|
|
result = await self._api_client.create_meeting_token(request)
|
|
return result.token
|
|
|
|
async def close(self):
|
|
"""Clean up API client resources."""
|
|
await self._api_client.close()
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
await self.close()
|