def disconnect(self, close_code): if self.breakout_id: try: breakout = Breakout.objects.get(pk=self.breakout_id) except Breakout.DoesNotExist: breakout = None track("leave_breakout", self.scope['user'], breakout=breakout)
def connect(self): self.slug = self.scope['url_route']['kwargs']['slug'] try: plenary = Plenary.objects.get(slug=self.slug) except Plenary.DoesNotExist: return self.handle_error('Plenary not found') # Remove previous presence if the user is reconnecting if self.scope['user'].is_authenticated: Presence.objects.filter( room__channel_name=plenary.channel_group_name, user=self.scope['user']).delete() # Handle max connections if plenary.max_participants > 0 and not plenary.has_admin( self.scope['user']): num_present = Presence.objects.filter( room__channel_name=plenary.channel_group_name).count() if num_present > plenary.max_participants: return self.close() if plenary.open and not self.scope['user'].is_authenticated: return self.handle_error( "Authentication required to connect to open plenaries") self.accept() # This joins the consumer's channel to the group for this plenary Room.objects.add(plenary.channel_group_name, self.channel_name, self.scope['user']) track("join_plenary", self.scope['user'], plenary=plenary)
def handle_contact_card(self, data, plenary): payload = data['payload'] user = self.scope['user'] keys = [ 'receive_wrapup_emails', 'email', 'contact_card_email', 'contact_card_twitter', ] for key in keys: if key in payload: setattr(user, key, payload[key]) try: user.full_clean() except ValidationError as e: return self.handle_error(json_dumps(e.message_dict)) user.save() track("change_contact_card", self.scope['user'], plenary=plenary) msg = prepare_message(type="auth", payload=serialize_auth_state(user, plenary)) self.send(text_data=msg['text']) for room in Room.objects.filter(presence__user__id=user.id).distinct(): broadcast(room.channel_name, type="users", payload={user.id: user.serialize_public()})
def _join_error(self, error_code, error_msg): data = { 'channel_name': self.channel_name, 'members': [], 'error': error_msg, "error_code": error_code, } #self.send(text_data=prepare_message(type='presence', payload=data).get('text_data')) self.send(text_data=json.dumps({'type': 'presence', 'payload': data})) track("error", self.scope['user'], data)
def handle_message_breakouts(self, data, plenary): if not plenary.has_admin(self.scope['user']): return self.handle_error("Must be an admin to message breakouts") msg_text = data['payload']['message'] for breakout in plenary.breakout_set.active(): broadcast(breakout.channel_group_name, type='message_breakouts', payload={'message': msg_text}) track("message_breakouts", self.scope['user'], {'message': msg_text}, plenary=plenary)
def handle_embeds(self, data, plenary): if not plenary.has_admin(self.scope['user']): return self.handle_error("Must be an admin to set embeds") error = None clean = [] for embed in data.get('payload', {}).get('embeds', []): if not isinstance(embed, dict): error = "Malformed embed" elif not isinstance(embed.get('props'), dict): error = "Malformed embed: missing props" elif embed.get('type') not in ("youtube", "url"): error = "Invalid type: {}".format(embed['type']) else: parsed = urlparse(embed['props']['src']) if parsed.scheme != "https": error = "Only https URLs allowed" if error: return self.handle_error(error) else: clean.append({ 'props': { 'src': embed['props']['src'] }, 'type': embed['type'] }) current = data['payload'].get('current', None) if current is not None and not isinstance(current, int): return self.handle_error("Invalid 'current' type") if isinstance(current, int) and (current < 0 or current > len(clean)): return self.handle_error("Invalid 'current' value") # Stop any current video sync if we're changing the current embed. if plenary.embeds and plenary.embeds['current'] != current: VideoSync.objects.pause_for_all(plenary.channel_group_name) plenary.embeds = {'embeds': clean, 'current': current} plenary.full_clean() plenary.save() broadcast(plenary.channel_group_name, type='embeds', payload=plenary.embeds) track("change_embeds", self.scope['user'], plenary.embeds, plenary=plenary)
def handle_chat(self, data, plenary): highlight = ( data['payload'].get('highlight') and \ (self.scope['user'].is_superuser or plenary.has_admin(self.scope['user'])) ) with transaction.atomic(): chat_message = ChatMessage.objects.create( plenary=plenary, user=self.scope['user'], message=data['payload']['message'], highlight=highlight or False) # He comes. https://stackoverflow.com/a/1732454 user_ids = re.findall( r'''<span [^>]*(?<= )data-mention-user-id=['"](\d+)['"][^>]*>''', data['payload']['message']) mentions = plenary.associated_users().filter(id__in=user_ids) chat_message.mentions.set(mentions) data = chat_message.serialize() broadcast(plenary.channel_group_name, type='chat', payload=data) track("plenary_chat", self.scope['user'], data, plenary=plenary)
def connect_to_breakout(self, breakout): # delete previous presences that a user might have had for this channel Presence.objects.filter(room__channel_name=breakout.channel_group_name, user=self.scope['user']).delete() num_connections = Presence.objects.filter( room__channel_name=breakout.channel_group_name).count() # Enforce max attendees. if num_connections >= breakout.max_attendees: return self.send_over_capacity_error() elif self.scope['user'].is_authenticated and Presence.objects.filter( room__channel_name__startswith=Breakout. CHANNEL_GROUP_NAME_PREFIX, user=self.scope['user']).exists(): # Only one connection per user. return self.send_already_connected_error() Room.objects.add(breakout.channel_group_name, self.channel_name, self.scope['user']) track("join_breakout", self.scope['user'], breakout=breakout)
def handle_video_sync(self, data, plenary): if not plenary.has_admin(self.scope['user']): return self.handle_error("Must be an admin to control video sync") payload = data['payload'] # Here, we're ignoring the payload['sync_id'] value and instead just # reusing plenary.channel_group_name as the sync_id. This is a convenient # way to ensure that the sync_id is the correct one for the plenary, and # prevents admins from abusing other rooms. But we'll need to change this # if we ever want more than one video container with sync on a plenary at # once. if payload['action'] == "play": time_index = payload.get('time_index', 0) VideoSync.objects.start( sync_id=plenary.channel_group_name, channel_group_name=plenary.channel_group_name, time_index=time_index) track("start_play_for_all", self.scope['user'], plenary=plenary) elif payload['action'] == "pause": VideoSync.objects.pause_for_all(sync_id=plenary.channel_group_name) track("stop_play_for_all", self.scope['user'], plenary=plenary) elif payload['action'] == "endSync": VideoSync.objects.end_sync(sync_id=plenary.channel_group_name)
def handle_error(self, error): data = prepare_message(type='error', error=error) self.send(text_data=data['text']) track("error", self.scope['user'], data)
def handle_record_speaker_stats(self, data, breakout): track("record_speaker_stats", self.scope['user'], {'speakerStats': data['payload']['speakerStats']}, breakout=breakout)
def disconnect(self, close_code): try: plenary = Plenary.objects.get(slug=self.slug) except Plenary.DoesNotExist: return self.handle_error('Plenary not found') track("leave_plenary", self.scope['user'], plenary=plenary)
def handle_breakout(self, data, plenary): is_admin = plenary.has_admin(self.scope['user']) admin_required_error = lambda: self.handle_error( "Must be an admin to do that.") payload = data['payload'] action = payload['action'] if not is_admin and plenary.breakout_mode != "user": return admin_required_error() def respond_with_breakouts(): # Not-too-efficient strategy: always respond with the full list of # breakouts from a new database query. We can optimize this later if # needed. broadcast( plenary.channel_group_name, type='breakout_receive', payload=[b.serialize() for b in plenary.breakout_set.active()]) # Handle actions if action == 'create': if not is_admin and plenary.breakout_mode != "user": return admin_required_error() if 'title' not in payload: return self.handle_error("Missing 'title'") if is_admin: etherpad_initial_text = payload.get('etherpad_initial_text', '') else: etherpad_initial_text = None breakout = Breakout( plenary=plenary, title=payload['title'], etherpad_initial_text=etherpad_initial_text, max_attendees=payload.get('max_attendees') or 10, is_proposal=(not is_admin or payload.get('is_proposal', False)), proposed_by=self.scope['user']) breakout.full_clean() breakout.save() if breakout.is_proposal: track("propose_breakout", self.scope['user'], breakout=breakout) return respond_with_breakouts() # For all actions other than create, we expect payload['id'] to contain the # id of the breakout to operate on. try: breakout = plenary.breakout_set.active().get(id=payload['id']) except (Breakout.DoesNotExist, KeyError): return self.handle_error("Breakout not found.") if action == 'delete': if not is_admin: return admin_required_error() breakout.active = False breakout.save() return respond_with_breakouts() elif action == 'modify': can_modify = (is_admin or (plenary.breakout_mode == "user" and breakout.proposed_by == self.scope['user'])) if not can_modify: if plenary.breakout_mode == "user": return self.handle_error( "Must be breakout proposer or admin to do that") return admin_required_error() if 'title' in payload: breakout.title = payload['title'] if is_admin and 'max_attendees' in payload: breakout.max_attendees = payload['max_attendees'] breakout.save() return respond_with_breakouts() elif action == 'approve': if not is_admin: return admin_required_error() breakout.is_proposal = not breakout.is_proposal breakout.save() return respond_with_breakouts() elif action == 'vote': if plenary.breakout_mode != "user": return self.handle_error( "Can only vote on user-proposed breakouts") # Toggle vote if breakout.votes.filter(pk=self.scope['user'].pk).exists(): breakout.votes.remove(self.scope['user']) else: breakout.votes.add(self.scope['user']) track("change_breakout_vote", self.scope['user'], breakout=breakout) return respond_with_breakouts()