Пример #1
0
	def __init__(self, *args, **kwargs):
		self.closed_channels = None
		self.message_creator = WebRtcMessageCreator(None, None)
		super(MessagesHandler, self).__init__()
		self.webrtc_ids = {}
		self.id = None  # child init
		self.last_client_ping = 0
		self.user_id = 0  # anonymous by default
		self.ip = None
		from chat import global_redis
		self.async_redis_publisher = global_redis.async_redis_publisher
		self.sync_redis = global_redis.sync_redis
		self.channels = []
		self._logger = None
		self.async_redis = Client(host=REDIS_HOST, port=REDIS_PORT, selected_db=REDIS_DB)
		self.patch_tornadoredis()
		# input websocket messages handlers
		# The handler is determined by @VarNames.EVENT
		self.process_ws_message = {
			Actions.GET_MESSAGES: self.process_get_messages,
			Actions.GET_MESSAGES_BY_IDS: self.process_get_messages_by_ids,
			Actions.PRINT_MESSAGE: self.process_send_message,
			Actions.DELETE_ROOM: self.delete_room,
			Actions.EDIT_MESSAGE: self.edit_message,
			Actions.CREATE_ROOM: self.create_new_room,
			Actions.CREATE_CHANNEL: self.create_new_channel,
			Actions.SAVE_CHANNEL_SETTINGS: self.save_channels_settings,
			Actions.SAVE_ROOM_SETTINGS: self.save_room_settings,
			Actions.DELETE_CHANNEL: self.delete_channel,
			Actions.SET_USER_PROFILE: self.profile_save_user,
			Actions.SET_SETTINGS: self.profile_save_settings,
			Actions.INVITE_USER: self.invite_user,
			Actions.PING: self.respond_ping,
			Actions.PONG: self.process_pong_message,
			Actions.SEARCH_MESSAGES: self.search_messages,
			Actions.SYNC_HISTORY: self.sync_history,
			Actions.SHOW_I_TYPE: self.show_i_type,
		}
		# Handlers for redis messages, if handler returns true - message won't be sent to client
		# The handler is determined by @VarNames.EVENT
		self.process_pubsub_message = {
			Actions.CREATE_ROOM: self.send_client_new_channel,
			Actions.DELETE_ROOM: self.send_client_delete_channel,
			Actions.INVITE_USER: self.send_client_new_channel,
			Actions.ADD_INVITE: self.send_client_new_channel,
			Actions.PING: self.process_ping_message,
		}
Пример #2
0
class TornadoHandler(WebSocketHandler, WebRtcMessageHandler):
    def __init__(self, *args, **kwargs):
        super(TornadoHandler, self).__init__(*args, **kwargs)
        self.__connected__ = False
        self.restored_connection = False
        self.anti_spam = AntiSpam()

    @property
    def connected(self):
        return self.__connected__

    @connected.setter
    def connected(self, value):
        self.__connected__ = value

    def data_received(self, chunk):
        pass

    def on_message(self, json_message):
        message = None
        try:
            if not self.connected:
                raise ValidationError(
                    'Skipping message %s, as websocket is not initialized yet'
                    % json_message)
            if not json_message:
                raise Exception('Skipping null message')
            # self.anti_spam.check_spam(json_message)
            self.logger.debug('<< %.1000s', json_message)
            message = json.loads(json_message)
            if message[VarNames.EVENT] not in self.process_ws_message:
                raise Exception("event {} is unknown".format(
                    message[VarNames.EVENT]))
            channel = message.get(VarNames.ROOM_ID)
            if channel and channel not in self.channels:
                raise ValidationError(
                    'Access denied for channel {}. Allowed channels: {}'.
                    format(channel, self.channels))
            self.process_ws_message[message[VarNames.EVENT]](message)
        except ValidationError as e:
            error_message = self.message_creator.default(
                str(e.message), Actions.GROWL_ERROR_MESSAGE, HandlerNames.WS)
            if message:
                error_message[VarNames.JS_MESSAGE_ID] = message.get(
                    VarNames.JS_MESSAGE_ID, None)
            self.ws_write(error_message)

    def on_close(self):
        if self.async_redis.subscribed:
            self.logger.info("Close event, unsubscribing from %s",
                             self.channels)
            self.async_redis.unsubscribe(self.channels)
        else:
            self.logger.info("Close event, not subscribed, channels: %s",
                             self.channels)
        self.async_redis_publisher.srem(RedisPrefix.ONLINE_VAR, self.id)
        online = self.get_dict_users_from_redis()
        my_online = online.setdefault(self.user_id, [])
        if self.id in my_online:
            my_online.remove(self.id)
        if self.connected:
            message = self.message_creator.room_online_logout(online)
            self.publish(message, settings.ALL_ROOM_ID)
            res = execute_query(settings.UPDATE_LAST_READ_MESSAGE, [
                self.user_id,
            ])
            self.logger.info("Updated %s last read message", res)
        self.disconnect()

    def disconnect(self, tries=0):
        """
		Closes a connection if it's not in proggress, otherwice timeouts closing
		https://github.com/evilkost/brukva/issues/25#issuecomment-9468227
		"""
        self.connected = False
        self.closed_channels = self.channels
        self.channels = []
        if self.async_redis.connection.in_progress and tries < 1000:  # failsafe eternal loop
            self.logger.debug('Closing a connection timeouts')
            ioloop.IOLoop.instance().add_timeout(timedelta(0.00001),
                                                 self.disconnect, tries + 1)
        else:
            self.logger.info("Close connection result: %s")
            self.async_redis.disconnect()

    def generate_self_id(self):
        """
		When user opens new tab in browser wsHandler.wsConnectionId stores Id of current ws
		So if ws loses a connection it still can reconnect with same id,
		and TornadoHandler can restore webrtc_connections to previous state
		"""
        conn_arg = self.get_argument('id', None)
        if conn_arg:
            random = conn_arg.split(":")[1]
        else:
            random = None
        self.id, random = create_id(self.user_id, random)
        self.restored_connection = random == conn_arg
        self.restored_connection = False
        self.save_ip()

    def open(self):
        session_key = self.get_argument('sessionId', None)
        user_id = self.sync_redis.hget('sessions', session_key)
        if user_id is None:
            self.logger.warning('!! Session key %s has been rejected' %
                                session_key)
            self.close(403, "Session key %s has been rejected" % session_key)
            return
        self.user_id = int(user_id)
        self.ip = self.get_client_ip()
        user_db = UserProfile.objects.get(id=self.user_id)
        self.generate_self_id()
        self.message_creator = WebRtcMessageCreator(self.user_id, self.id)
        self._logger = logging.LoggerAdapter(parent_logger, {
            'id': self.id,
            'ip': self.ip
        })
        self.logger.debug("!! Incoming connection, session %s, thread hash %s",
                          session_key, self.id)
        self.async_redis.connect()
        self.async_redis_publisher.sadd(RedisPrefix.ONLINE_VAR, self.id)
        # since we add user to online first, latest trigger will always show correct online

        online = self.get_dict_users_from_redis()
        # current user is already online
        my_online = online.setdefault(self.user_id, [])
        if self.id not in my_online:
            my_online.append(self.id)

        was_online = len(online.get(
            self.user_id)) > 1  # if other tabs are opened
        user_rooms_query = Room.objects.filter(users__id=self.user_id, disabled=False) \
         .values('id', 'name', 'creator_id', 'channel_id', 'p2p', 'roomusers__notifications', 'roomusers__volume')
        room_users = [{
            VarNames.ROOM_ID: room['id'],
            VarNames.ROOM_NAME: room['name'],
            VarNames.CHANNEL_ID: room['channel_id'],
            VarNames.ROOM_CREATOR_ID: room['creator_id'],
            VarNames.NOTIFICATIONS: room['roomusers__notifications'],
            VarNames.P2P: room['p2p'],
            VarNames.VOLUME: room['roomusers__volume'],
            VarNames.ROOM_USERS: []
        } for room in user_rooms_query]
        user_rooms_dict = {room[VarNames.ROOM_ID]: room for room in room_users}
        channels_ids = [
            channel[VarNames.CHANNEL_ID] for channel in room_users
            if channel[VarNames.CHANNEL_ID]
        ]
        channels_db = Channel.objects.filter(Q(id__in=channels_ids)
                                             | Q(creator=self.user_id),
                                             disabled=False)
        channels = [{
            VarNames.CHANNEL_ID: channel.id,
            VarNames.CHANNEL_NAME: channel.name,
            VarNames.CHANNEL_CREATOR_ID: channel.creator_id
        } for channel in channels_db]
        room_ids = [room_id[VarNames.ROOM_ID] for room_id in room_users]
        rooms_users = RoomUsers.objects.filter(room_id__in=room_ids).values(
            'user_id', 'room_id')
        for ru in rooms_users:
            user_rooms_dict[ru['room_id']][VarNames.ROOM_USERS].append(
                ru['user_id'])
        # get all missed messages
        self.channels = room_ids  # py2 doesn't support clear()
        self.channels.append(self.channel)
        self.channels.append(self.id)
        self.listen(self.channels)
        # this was replaced to syncHistory method that's called from browser and passes existing ids
        # off_messages, history = self.get_offline_messages(room_users, was_online, self.get_argument('history', False))
        # for room in room_users:
        # 	room_id = room[VarNames.ROOM_ID]
        # 	h = history.get(room_id)
        # 	o = off_messages.get(room_id)
        # 	if h:
        # 		room[VarNames.LOAD_MESSAGES_HISTORY] = h
        # 	if o:
        # 		room[VarNames.LOAD_MESSAGES_OFFLINE] = o

        if settings.SHOW_COUNTRY_CODE:
            fetched_users = User.objects.annotate(user_c=Count('id')).values(
                'id', 'username', 'sex', 'userjoinedinfo__ip__country_code',
                'userjoinedinfo__ip__country', 'userjoinedinfo__ip__region',
                'userjoinedinfo__ip__city')
            user_dict = [
                RedisPrefix.set_js_user_structure_flag(
                    user['id'], user['username'], user['sex'],
                    user['userjoinedinfo__ip__country_code'],
                    user['userjoinedinfo__ip__country'],
                    user['userjoinedinfo__ip__region'],
                    user['userjoinedinfo__ip__city']) for user in fetched_users
            ]
        else:
            fetched_users = User.objects.values('id', 'username', 'sex')
            user_dict = [
                RedisPrefix.set_js_user_structure(user['id'], user['username'],
                                                  user['sex'])
                for user in fetched_users
            ]

        self.ws_write(
            self.message_creator.set_room(room_users, user_dict, online,
                                          user_db, channels))
        online_user_names_mes = self.message_creator.room_online_login(
            online, user_db.username, user_db.sex_str)
        self.logger.info('!! First tab, sending refresh online for all')
        self.publish(online_user_names_mes, settings.ALL_ROOM_ID)
        self.logger.info("!! User %s subscribes for %s", self.user_id,
                         self.channels)
        self.connected = True

    # def get_offline_messages(self, user_rooms, was_online, with_history):
    # 	q_objects = get_history_message_query(self.get_argument('messages', None), user_rooms, with_history)
    # 	if was_online:
    # 		off_messages = []
    # 	else:
    # 		off_messages = Message.objects.filter(
    # 			id__gt=F('room__roomusers__last_read_message_id'),
    # 			room__roomusers__user_id=self.user_id
    # 		)
    # 	off = {}
    # 	history = {}
    # 	if len(q_objects.children) > 0:
    # 		history_messages = Message.objects.filter(q_objects)
    # 		all = list(chain(off_messages, history_messages))
    # 		self.logger.info("Offline messages IDs: %s, history messages: %s", [m.id for m in off_messages], [m.id for m in history_messages])
    # 	else:
    # 		history_messages = []
    # 		all = off_messages
    # 	if self.restored_connection:
    # 		off_messages = all
    # 		history_messages = []
    # 	imv = get_message_images_videos(all)
    # 	self.set_video_images_messages(imv, off_messages, off)
    # 	self.set_video_images_messages(imv, history_messages, history)
    # 	return off, history

    def check_origin(self, origin):
        """
		check whether browser set domain matches origin
		"""
        return True  # we don't use cookies

    @gen.coroutine
    def save_ip(self):
        """
		This code is not used anymore
		"""
        if not UserJoinedInfo.objects.filter(
                Q(ip__ip=self.ip) & Q(user_id=self.user_id)).exists():
            ip = yield from get_or_create_ip_model(self.ip, self.logger)
            UserJoinedInfo.objects.create(ip=ip, user_id=self.user_id)

    def ws_write(self, message):
        """
		Tries to send message, doesn't throw exception outside
		:type self: MessagesHandler
		:type message object
		"""
        # self.logger.debug('<< THREAD %s >>', os.getppid())
        try:
            if isinstance(message, dict):
                message = json.dumps(message)
            if not isinstance(message, str_type):
                raise ValueError('Wrong message type : %s' % str(message))
            self.logger.debug(">> %.1000s", message)
            self.write_message(message)
        except WebSocketClosedError as e:
            self.logger.warning("%s. Can't send message << %s >> ", e,
                                str(message))

    def get_client_ip(self):
        return self.request.headers.get("X-Real-IP") or self.request.remote_ip
Пример #3
0
    def open(self):
        session_key = self.get_argument('sessionId', None)
        user_id = self.sync_redis.hget('sessions', session_key)
        if user_id is None:
            self.logger.warning('!! Session key %s has been rejected' %
                                session_key)
            self.close(403, "Session key %s has been rejected" % session_key)
            return
        self.user_id = int(user_id)
        self.ip = self.get_client_ip()
        user_db = UserProfile.objects.get(id=self.user_id)
        self.generate_self_id()
        self.message_creator = WebRtcMessageCreator(self.user_id, self.id)
        self._logger = logging.LoggerAdapter(parent_logger, {
            'id': self.id,
            'ip': self.ip
        })
        self.logger.debug("!! Incoming connection, session %s, thread hash %s",
                          session_key, self.id)
        self.async_redis.connect()
        self.async_redis_publisher.sadd(RedisPrefix.ONLINE_VAR, self.id)
        # since we add user to online first, latest trigger will always show correct online

        online = self.get_dict_users_from_redis()
        # current user is already online
        my_online = online.setdefault(self.user_id, [])
        if self.id not in my_online:
            my_online.append(self.id)

        was_online = len(online.get(
            self.user_id)) > 1  # if other tabs are opened
        user_rooms_query = Room.objects.filter(users__id=self.user_id, disabled=False) \
         .values('id', 'name', 'creator_id', 'channel_id', 'p2p', 'roomusers__notifications', 'roomusers__volume')
        room_users = [{
            VarNames.ROOM_ID: room['id'],
            VarNames.ROOM_NAME: room['name'],
            VarNames.CHANNEL_ID: room['channel_id'],
            VarNames.ROOM_CREATOR_ID: room['creator_id'],
            VarNames.NOTIFICATIONS: room['roomusers__notifications'],
            VarNames.P2P: room['p2p'],
            VarNames.VOLUME: room['roomusers__volume'],
            VarNames.ROOM_USERS: []
        } for room in user_rooms_query]
        user_rooms_dict = {room[VarNames.ROOM_ID]: room for room in room_users}
        channels_ids = [
            channel[VarNames.CHANNEL_ID] for channel in room_users
            if channel[VarNames.CHANNEL_ID]
        ]
        channels_db = Channel.objects.filter(Q(id__in=channels_ids)
                                             | Q(creator=self.user_id),
                                             disabled=False)
        channels = [{
            VarNames.CHANNEL_ID: channel.id,
            VarNames.CHANNEL_NAME: channel.name,
            VarNames.CHANNEL_CREATOR_ID: channel.creator_id
        } for channel in channels_db]
        room_ids = [room_id[VarNames.ROOM_ID] for room_id in room_users]
        rooms_users = RoomUsers.objects.filter(room_id__in=room_ids).values(
            'user_id', 'room_id')
        for ru in rooms_users:
            user_rooms_dict[ru['room_id']][VarNames.ROOM_USERS].append(
                ru['user_id'])
        # get all missed messages
        self.channels = room_ids  # py2 doesn't support clear()
        self.channels.append(self.channel)
        self.channels.append(self.id)
        self.listen(self.channels)
        # this was replaced to syncHistory method that's called from browser and passes existing ids
        # off_messages, history = self.get_offline_messages(room_users, was_online, self.get_argument('history', False))
        # for room in room_users:
        # 	room_id = room[VarNames.ROOM_ID]
        # 	h = history.get(room_id)
        # 	o = off_messages.get(room_id)
        # 	if h:
        # 		room[VarNames.LOAD_MESSAGES_HISTORY] = h
        # 	if o:
        # 		room[VarNames.LOAD_MESSAGES_OFFLINE] = o

        if settings.SHOW_COUNTRY_CODE:
            fetched_users = User.objects.annotate(user_c=Count('id')).values(
                'id', 'username', 'sex', 'userjoinedinfo__ip__country_code',
                'userjoinedinfo__ip__country', 'userjoinedinfo__ip__region',
                'userjoinedinfo__ip__city')
            user_dict = [
                RedisPrefix.set_js_user_structure_flag(
                    user['id'], user['username'], user['sex'],
                    user['userjoinedinfo__ip__country_code'],
                    user['userjoinedinfo__ip__country'],
                    user['userjoinedinfo__ip__region'],
                    user['userjoinedinfo__ip__city']) for user in fetched_users
            ]
        else:
            fetched_users = User.objects.values('id', 'username', 'sex')
            user_dict = [
                RedisPrefix.set_js_user_structure(user['id'], user['username'],
                                                  user['sex'])
                for user in fetched_users
            ]

        self.ws_write(
            self.message_creator.set_room(room_users, user_dict, online,
                                          user_db, channels))
        online_user_names_mes = self.message_creator.room_online_login(
            online, user_db.username, user_db.sex_str)
        self.logger.info('!! First tab, sending refresh online for all')
        self.publish(online_user_names_mes, settings.ALL_ROOM_ID)
        self.logger.info("!! User %s subscribes for %s", self.user_id,
                         self.channels)
        self.connected = True
Пример #4
0
class MessagesHandler():

	def __init__(self, *args, **kwargs):
		self.closed_channels = None
		self.message_creator = WebRtcMessageCreator(None, None)
		super(MessagesHandler, self).__init__()
		self.webrtc_ids = {}
		self.id = None  # child init
		self.last_client_ping = 0
		self.user_id = 0  # anonymous by default
		self.ip = None
		from chat import global_redis
		self.async_redis_publisher = global_redis.async_redis_publisher
		self.sync_redis = global_redis.sync_redis
		self.channels = []
		self._logger = None
		self.async_redis = Client(host=REDIS_HOST, port=REDIS_PORT, selected_db=REDIS_DB)
		self.patch_tornadoredis()
		# input websocket messages handlers
		# The handler is determined by @VarNames.EVENT
		self.process_ws_message = {
			Actions.GET_MESSAGES: self.process_get_messages,
			Actions.GET_MESSAGES_BY_IDS: self.process_get_messages_by_ids,
			Actions.PRINT_MESSAGE: self.process_send_message,
			Actions.DELETE_ROOM: self.delete_room,
			Actions.EDIT_MESSAGE: self.edit_message,
			Actions.CREATE_ROOM: self.create_new_room,
			Actions.CREATE_CHANNEL: self.create_new_channel,
			Actions.SAVE_CHANNEL_SETTINGS: self.save_channels_settings,
			Actions.SAVE_ROOM_SETTINGS: self.save_room_settings,
			Actions.DELETE_CHANNEL: self.delete_channel,
			Actions.SET_USER_PROFILE: self.profile_save_user,
			Actions.SET_SETTINGS: self.profile_save_settings,
			Actions.INVITE_USER: self.invite_user,
			Actions.PING: self.respond_ping,
			Actions.PONG: self.process_pong_message,
			Actions.SEARCH_MESSAGES: self.search_messages,
			Actions.SYNC_HISTORY: self.sync_history,
			Actions.SHOW_I_TYPE: self.show_i_type,
		}
		# Handlers for redis messages, if handler returns true - message won't be sent to client
		# The handler is determined by @VarNames.EVENT
		self.process_pubsub_message = {
			Actions.CREATE_ROOM: self.send_client_new_channel,
			Actions.DELETE_ROOM: self.send_client_delete_channel,
			Actions.INVITE_USER: self.send_client_new_channel,
			Actions.ADD_INVITE: self.send_client_new_channel,
			Actions.PING: self.process_ping_message,
		}

	def patch_tornadoredis(self):  # TODO remove this
		fabric = type(self.async_redis.connection.readline)
		self.async_redis.connection.old_read = self.async_redis.connection.readline

		def new_read(new_self, callback=None):
			try:
				return new_self.old_read(callback=callback)
			except Exception as e:
				return
				current_online = self.get_online_from_redis()
				self.logger.error(e)
				self.logger.error(
					"Exception info: "
					"self.id: %s ;;; "
					"self.connected = '%s';;; "
					"Redis default channel online = '%s';;; "
					"self.channels = '%s';;; "
					"self.closed_channels  = '%s';;;",
					self.id, self.connected, current_online, self.channels, self.closed_channels
				)
				raise e

		self.async_redis.connection.readline = fabric(new_read, self.async_redis.connection)

	@property
	def channel(self):
		return RedisPrefix.generate_user(self.user_id)

	@property
	def connected(self):
		raise NotImplemented

	@connected.setter
	def connected(self, value):
		raise NotImplemented

	@gen.engine
	def listen(self, channels):
		yield Task(
			self.async_redis.subscribe, channels)
		self.async_redis.listen(self.on_pub_sub_message)

	@property
	def logger(self):
		return self._logger if self._logger else base_logger

	@gen.engine
	def add_channel(self, channel):
		self.channels.append(channel)
		yield Task(self.async_redis.subscribe, (channel,))

	def get_online_from_redis(self):
		return self.get_online_and_status_from_redis()[1]

	def get_dict_users_from_redis(self):
		online = self.sync_redis.ssmembers(RedisPrefix.ONLINE_VAR)
		self.logger.debug('!! redis online: %s', online)
		result = self.parse_redis_online_into_dict_set(online) if online else {}
		return result

	@staticmethod
	def parse_redis_online_into_dict_set(online):
		"""
		:rtype : Dict[int, set]
		"""
		result = {}
		for decoded in online:  # py2 iteritems
			user_id = decoded.split(':')[0]
			result.setdefault(int(user_id), []).append(decoded)
		return result

	def get_online_and_status_from_redis(self):
		"""
		:rtype : (bool, list)
		"""
		online = self.sync_redis.ssmembers(RedisPrefix.ONLINE_VAR)
		self.logger.debug('!! redis online: %s', online)
		return self.parse_redis_online(online) if online else (False, [])

	def parse_redis_online(self, online):
		"""
		:rtype : (bool, list)
		"""
		result = set()
		user_is_online = False
		for decoded in online:  # py2 iteritems
			user_id = int(decoded.split(':')[0])
			if user_id == self.user_id and decoded != self.id:
				user_is_online = True
			result.add(user_id)
		return user_is_online, list(result)

	def publish(self, message, channel, parsable=False):
		jsoned_mess = encode_message(message, parsable)
		self.raw_publish(jsoned_mess, channel)

	def raw_publish(self, jsoned_mess, channel):
		self.logger.debug('<%s> %s', channel, jsoned_mess)
		self.async_redis_publisher.publish(channel, jsoned_mess)

	def on_pub_sub_message(self, message):
		"""
		All pubsub messages are automatically sent to client.
		:param message:
		:return:
		"""
		data = message.body
		if isinstance(data, str_type):  # not subscribe event
			prefixless_str = remove_parsable_prefix(data)
			if prefixless_str:
				dict_message = json.loads(prefixless_str)
				res = self.process_pubsub_message[dict_message[VarNames.EVENT]](dict_message)
				if not res:
					self.ws_write(prefixless_str)
			else:
				self.ws_write(data)

	def ws_write(self, message):
		raise NotImplementedError('WebSocketHandler implements')

	def search_giphy(self, message, query, cb):
		self.logger.debug("!! Asking giphy for: %s", query)
		def on_giphy_reply(response):
			try:
				self.logger.debug("!! Got giphy response: " + str(response.body))
				res =  json.loads(response.body)
				giphy = res['data'][0]['images']['downsized_medium']['url']
			except:
				giphy = None
			cb(message, giphy)
		url = GIPHY_URL.format(GIPHY_API_KEY, quote(query, safe=''))
		http_client.fetch(url, callback=on_giphy_reply)

	def notify_offline(self, channel, message_id):
		if FIREBASE_API_KEY is None:
			return
		online = self.get_online_from_redis()
		if channel == ALL_ROOM_ID:
			return
		offline_users = RoomUsers.objects.filter(room_id=channel, notifications=True).exclude(user_id__in=online).values_list('user_id')
		subscriptions = Subscription.objects.filter(user__in=offline_users, inactive=False)
		if len(subscriptions) == 0:
			return
		new_sub_mess =[SubscriptionMessages(message_id=message_id, subscription_id=r.id) for r in subscriptions]
		reg_ids =[r.registration_id for r in subscriptions]
		SubscriptionMessages.objects.bulk_create(new_sub_mess)
		self.post_firebase(list(reg_ids))

	def post_firebase(self, reg_ids):
		def on_reply(response):
			try:
				self.logger.debug("!! FireBase response: " + str(response.body))
				response_obj = json.loads(response.body)
				delete = []
				for index, elem in enumerate(response_obj['results']):
					if elem.get('error') in ['NotRegistered', 'InvalidRegistration']:
						delete.append(reg_ids[index])
				if len(delete) > 0:
					self.logger.info("Deactivating subscriptions: %s", delete)
					Subscription.objects.filter(registration_id__in=delete).update(inactive=True)
			except Exception as e:
				self.logger.error("Unable to parse response" + str(e))
				pass

		headers = {"Content-Type": "application/json", "Authorization": "key=%s" % FIREBASE_API_KEY}
		body = json.dumps({"registration_ids": reg_ids})
		self.logger.debug("!! post_fire_message %s", body)

		# TODO
		# webpush(subscription_info,
		# 		  data,
		# 		  vapid_private_key="Private Key or File Path[1]",
		# 		  vapid_claims={"sub": "mailto:YourEmailAddress"})

		r = HTTPRequest(FIREBASE_URL, method="POST", headers=headers, body=body)
		http_client.fetch(r, callback=on_reply)

	def isGiphy(self, content):
		if GIPHY_API_KEY is not None and content is not None:
			giphy_match = re.search(GIPHY_REGEX, content)
			return giphy_match.group(1) if giphy_match is not None else None

	def process_send_message(self, message):
		"""
		:type message: dict
		"""
		content = message.get(VarNames.CONTENT)
		giphy_match = self.isGiphy(content)

		# @transaction.atomic mysql has gone away
		def send_message(message, giphy=None):
			if message[VarNames.TIME_DIFF] < 0:
				raise ValidationError("Back to the future?")
			tags_users = message[VarNames.MESSAGE_TAGS]
			files = UploadedFile.objects.filter(id__in=message.get(VarNames.FILES), user_id=self.user_id)
			symbol = max_from_2(get_max_symbol(files), get_max_symbol_dict(tags_users))
			channel = message[VarNames.ROOM_ID]
			js_id = message[VarNames.JS_MESSAGE_ID]
			parent_message_id = message[VarNames.PARENT_MESSAGE]
			if parent_message_id:
				parent_room_id = Message.objects.get(id=parent_message_id).room_id
				if parent_room_id not in self.channels:
					raise ValidationError("You don't have access to this room message")
			message_db = Message(
				sender_id=self.user_id,
				content=message[VarNames.CONTENT],
				symbol=symbol,
				parent_message_id=parent_message_id,
				giphy=giphy,
				room_id=channel
			)
			message_db.time -= message[VarNames.TIME_DIFF]
			res_files = []
			message_db.save()

			if tags_users:
				mes_ment = [MessageMention(
					user_id=userId,
					message_id=message_db.id,
					symbol=symb,
				) for symb, userId in tags_users.items()]
				MessageMention.objects.bulk_create(mes_ment)
			if files:
				images = up_files_to_img(files, message_db.id)
				res_files = MessagesCreator.prepare_img_video(images, message_db.id)
			prepared_message = self.message_creator.create_send_message(
				message_db,
				Actions.PRINT_MESSAGE,
				res_files,
				tags_users
			)
			prepared_message[VarNames.JS_MESSAGE_ID] = js_id
			self.publish(prepared_message, channel)
			self.notify_offline(channel, message_db.id)
		if giphy_match is not None:
			self.search_giphy(message, giphy_match, send_message)
		else:
			send_message(message)

	def save_channels_settings(self, message):
		channel_id = message[VarNames.CHANNEL_ID]
		channel_name = message[VarNames.CHANNEL_NAME]
		new_creator = message[VarNames.CHANNEL_CREATOR_ID]
		if not channel_name or len(channel_name) > 16:
			raise ValidationError('Incorrect channel name name "{}"'.format(channel_name))
		channel = Channel.objects.get(id=channel_id)
		users_id = list(RoomUsers.objects.filter(room__channel_id=channel_id).values_list('user_id', flat=True))
		if channel.creator_id != self.user_id and self.user_id not in users_id:
			raise ValidationError("You are not allowed to edit this channel")
		if new_creator != channel.creator_id:
			if self.user_id != channel.creator_id:
				raise ValidationError("Only admin can change the admin of this channel")
			if new_creator not in users_id:
				raise ValidationError("You can only change admin to one of the users in this channels room")
			channel.creator_id = new_creator
		if self.user_id not in users_id: # if channel has no rooms
			users_id.append(self.user_id)
		channel.name = channel_name
		channel.save()
		m = {
			VarNames.EVENT: Actions.SAVE_CHANNEL_SETTINGS,
			VarNames.CHANNEL_ID: channel_id,
			VarNames.CB_BY_SENDER: self.id,
			VarNames.HANDLER_NAME: HandlerNames.ROOM,
			VarNames.CHANNEL_NAME: channel_name,
			VarNames.CHANNEL_CREATOR_ID: channel.creator_id,
			VarNames.TIME: get_milliseconds(),
			VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID],
		}
		for user_id in users_id:
			self.publish(m, RedisPrefix.generate_user(user_id))

	def create_new_channel(self, message):
		channel_name = message.get(VarNames.CHANNEL_NAME)
		if not channel_name or len(channel_name) > 16:
			raise ValidationError('Incorrect channel name name "{}"'.format(channel_name))

		channel = Channel(name=channel_name, creator_id=self.user_id)
		channel.save()
		channel_id = channel.id
		m = {
			VarNames.EVENT: Actions.CREATE_CHANNEL,
			VarNames.CHANNEL_ID: channel_id,
			VarNames.CHANNEL_CREATOR_ID: channel.creator_id,
			VarNames.CB_BY_SENDER: self.id,
			VarNames.HANDLER_NAME: HandlerNames.ROOM,
			VarNames.CHANNEL_NAME: channel_name,
			VarNames.TIME: get_milliseconds(),
			VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID],
		}
		self.publish(m, self.channel)
		
	def create_new_room(self, message):
		room_name = message.get(VarNames.ROOM_NAME)
		users = message.get(VarNames.ROOM_USERS)
		channel_id = message.get(VarNames.CHANNEL_ID)
		users.append(self.user_id)
		users = list(set(users))
		if room_name and len(room_name) > 16:
			raise ValidationError('Incorrect room name "{}"'.format(room_name))
		create_room = True
		if not room_name and len(users) == 2:
			user_rooms = evaluate(Room.users.through.objects.filter(user_id=self.user_id, room__name__isnull=True).values('room_id'))
			user_id = users[0] if users[1] == self.user_id else users[1]
			try:
				room = RoomUsers.objects.filter(user_id=user_id, room__in=user_rooms).values('room__id', 'room__disabled').get()
				room_id = room['room__id']
				if room['room__disabled']: # only a private room can be disabled
					Room.objects.filter(id=room_id).update(disabled=False, p2p=message[VarNames.P2P])
					RoomUsers.objects.filter(
						user_id=self.user_id,
						room_id=room_id
					).update(
						volume=message[VarNames.VOLUME],
						notifications=message[VarNames.NOTIFICATIONS]
					)
				else:
					raise ValidationError('This room already exist')
				create_room = False
			except RoomUsers.DoesNotExist:
				pass
		elif not room_name:
			raise ValidationError('At least one user should be selected, or room should be public')

		if channel_id and channel_id not in self.get_users_channels_ids():
			raise ValidationError("You don't have access to this channel")
		if channel_id:
			channel = Channel.objects.get(id=channel_id)
			channel_name = channel.name
			channel_creator_id = channel.creator_id
		else:
			channel_name = None
			channel_creator_id = None
		if create_room:
			room = Room(name=room_name, channel_id=channel_id, p2p=message[VarNames.P2P])
			if not room_name:
				room.creator_id = self.user_id
			room.save()
			room_id = room.id
			max_id = Message.objects.all().aggregate(Max('id'))['id__max']
			ru = [RoomUsers(
				user_id=user_id,
				room_id=room_id,
				last_read_message_id=max_id,
				volume=message[VarNames.VOLUME],
				notifications=message[VarNames.NOTIFICATIONS]
			) for user_id in users]
			RoomUsers.objects.bulk_create(ru)

		m = {
			VarNames.EVENT: Actions.CREATE_ROOM,
			VarNames.ROOM_ID: room_id,
			VarNames.ROOM_USERS: users,
			VarNames.CB_BY_SENDER: self.id,
			VarNames.INVITER_USER_ID: self.user_id,
			VarNames.HANDLER_NAME: HandlerNames.ROOM,
			VarNames.VOLUME: message[VarNames.VOLUME],
			VarNames.P2P: message[VarNames.P2P],
			VarNames.NOTIFICATIONS: message[VarNames.NOTIFICATIONS],
			VarNames.ROOM_NAME: room_name,
			VarNames.TIME: get_milliseconds(),
			VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID],
		}
		if channel_id:
			m[VarNames.CHANNEL_NAME] = channel_name
			m[VarNames.CHANNEL_ID] = channel_id
			m[VarNames.CHANNEL_CREATOR_ID] = channel_creator_id
		jsoned_mess = encode_message(m, True)
		for user in users:
			self.raw_publish(jsoned_mess, RedisPrefix.generate_user(user))

	def save_room_settings(self, message):
		"""
		POST only, validates email during registration
		"""
		room_id = message[VarNames.ROOM_ID]
		room_name = message[VarNames.ROOM_NAME]
		creator_id = message.get(VarNames.ROOM_CREATOR_ID) # will be none for private room
		updated = RoomUsers.objects.filter(room_id=room_id, user_id=self.user_id).update(
			volume=message[VarNames.VOLUME],
			notifications=message[VarNames.NOTIFICATIONS]
		)
		if updated != 1:
			raise ValidationError("You don't have access to this room")
		room = Room.objects.get(id=room_id)
		update_all = False
		if not room.name:
			if room.p2p != message[VarNames.P2P]:
				room.p2p = message[VarNames.P2P]
				update_all = True
		elif room_id != settings.ALL_ROOM_ID:
			if room_name != room.name:
				room.name = room_name
				update_all = True

			if room.channel_id != message[VarNames.CHANNEL_ID]:
				room.channel_id = message[VarNames.CHANNEL_ID]
				if room.channel_id and room.channel_id not in self.get_users_channels_ids():
					raise ValidationError("You don't have access to this channel")
				update_all = True
			if creator_id != room.creator_id:
				if room.creator_id != self.user_id:
					raise ValidationError("Only an owner of this room can change its admin")
				users_id = RoomUsers.objects.filter(room_id=room.id).values_list('user_id', flat=True)
				if creator_id not in users_id:
					raise ValidationError("You can only change admin to one of the users in this channels room")
				room.creator_id = creator_id
				update_all = True
		if message.get(VarNames.CHANNEL_ID): # will be nOne for private room
			channel = Channel.objects.get(id=message[VarNames.CHANNEL_ID])
			channel_name = channel.name
			channel_creator_id = channel.creator_id
		else:
			channel_name = None
			channel_creator_id = None
		if update_all:
			room.save()
			room_users = list(RoomUsers.objects.filter(room_id=room_id))
			for room_user in room_users:
				self.publish({
					VarNames.EVENT: Actions.SAVE_ROOM_SETTINGS,
					VarNames.CHANNEL_ID: room.channel_id,
					VarNames.CB_BY_SENDER: self.id,
					VarNames.HANDLER_NAME: HandlerNames.ROOM,
					VarNames.CHANNEL_NAME: channel_name,
					VarNames.CHANNEL_CREATOR_ID: channel_creator_id,
					VarNames.ROOM_CREATOR_ID: room.creator_id,
					VarNames.ROOM_ID: room.id,
					VarNames.VOLUME: room_user.volume,
					VarNames.NOTIFICATIONS: room_user.notifications,
					VarNames.P2P: message[VarNames.P2P],
					VarNames.ROOM_NAME: room_name,
					VarNames.TIME: get_milliseconds(),
					VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID],
				}, RedisPrefix.generate_user(room_user.user_id))
		else:
			self.publish({
				VarNames.EVENT: Actions.SAVE_ROOM_SETTINGS,
				VarNames.CHANNEL_ID: room.channel_id,
				VarNames.CB_BY_SENDER: self.id,
				VarNames.CHANNEL_CREATOR_ID: channel_creator_id,
				VarNames.ROOM_CREATOR_ID: room.creator_id,
				VarNames.HANDLER_NAME: HandlerNames.ROOM,
				VarNames.CHANNEL_NAME: channel_name,
				VarNames.ROOM_ID: room.id,
				VarNames.VOLUME: message[VarNames.VOLUME],
				VarNames.NOTIFICATIONS: message[VarNames.NOTIFICATIONS],
				VarNames.P2P: message[VarNames.P2P],
				VarNames.ROOM_NAME: room_name,
				VarNames.TIME: get_milliseconds(),
				VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID],
			}, self.channel)

	def get_users_channels_ids(self):
		channels_ids = Room.objects.filter(users__id=self.user_id, disabled=False).values_list('channel_id', flat=True)
		return Channel.objects.filter(Q(id__in=channels_ids) | Q(creator=self.user_id), disabled=False).values_list('id', flat=True)

	def profile_save_settings(self, in_message):
		message = in_message[VarNames.CONTENT]
		UserProfile.objects.filter(id=self.user_id).update(
			suggestions=message[UserSettingsVarNames.SUGGESTIONS],
			embedded_youtube=message[UserSettingsVarNames.EMBEDDED_YOUTUBE],
			highlight_code=message[UserSettingsVarNames.HIGHLIGHT_CODE],
			message_sound=message[UserSettingsVarNames.MESSAGE_SOUND],
			incoming_file_call_sound=message[UserSettingsVarNames.INCOMING_FILE_CALL_SOUND],
			online_change_sound=message[UserSettingsVarNames.ONLINE_CHANGE_SOUND],
			show_when_i_typing=message[UserSettingsVarNames.SHOW_WHEN_I_TYPING],
			logs=message[UserSettingsVarNames.LOGS],
			send_logs=message[UserSettingsVarNames.SEND_LOGS],
			theme=message[UserSettingsVarNames.THEME],
		)
		self.publish(self.message_creator.set_settings(in_message[VarNames.JS_MESSAGE_ID], message), self.channel)

	def profile_save_user(self, in_message):
		message = in_message[VarNames.CONTENT]
		userprofile = UserProfile.objects.get(id=self.user_id)
		un = message[UserProfileVarNames.USERNAME]
		if userprofile.username != un:
			check_user(un)

		sex = message[UserProfileVarNames.SEX]
		UserProfile.objects.filter(id=self.user_id).update(
			username=un,
			name=message[UserProfileVarNames.NAME],
			city=message[UserProfileVarNames.CITY],
			surname=message[UserProfileVarNames.SURNAME],
			birthday=message[UserProfileVarNames.BIRTHDAY],
			contacts=message[UserProfileVarNames.CONTACTS],
			sex=settings.GENDERS_STR[sex],
		)
		self.publish(self.message_creator.set_user_profile(in_message[VarNames.JS_MESSAGE_ID], message), self.channel)
		if userprofile.sex_str != sex or userprofile.username != un:
			self.publish(self.message_creator.changed_user_profile(sex, self.user_id, un), settings.ALL_ROOM_ID)

	def profile_save_image(self, request):
		pass
		# UserProfile.objects.filter(id=request.user.id).update(
		# 	suggestions=request.POST['suggestions'],
		# 	embedded_youtube=request.POST['embedded_youtube'],
		# 	highlight_code=request.POST['highlight_code'],
		# 	message_sound=request.POST['message_sound'],
		# 	incoming_file_call_sound=request.POST['incoming_file_call_sound'],
		# 	online_change_sound=request.POST['online_change_sound'],
		# 	logs=request.POST['logs'],
		# 	send_logs=request.POST['send_logs'],
		# 	theme=request.POST['theme'],
		# )
		# return HttpResponse(settings.VALIDATION_IS_OK, content_type='text/plain')

	def invite_user(self, message):
		room_id = message[VarNames.ROOM_ID]
		if room_id not in self.channels:
			raise ValidationError("Access denied, only allowed for channels {}".format(self.channels))
		room = Room.objects.get(id=room_id)
		if room.is_private:
			raise ValidationError("You can't add users to direct room, create a new room instead")
		users = message.get(VarNames.ROOM_USERS)
		users_in_room = list(RoomUsers.objects.filter(room_id=room_id).values_list('user_id', flat=True))
		intersect = set(users_in_room) & set(users)
		if bool(intersect):
			raise ValidationError("Users %s are already in the room", intersect)
		users_in_room.extend(users)

		max_id = Message.objects.filter(room_id=room_id).aggregate(Max('id'))['id__max']
		if not max_id:
			max_id = Message.objects.all().aggregate(Max('id'))['id__max']
		ru = [RoomUsers(
			user_id=user_id,
			room_id=room_id,
			last_read_message_id=max_id,
			volume=1,
			notifications=False
		) for user_id in users]
		RoomUsers.objects.bulk_create(ru)

		add_invitee = {
			VarNames.EVENT: Actions.ADD_INVITE,
			VarNames.ROOM_ID: room_id,
			VarNames.ROOM_USERS: users_in_room,
			VarNames.ROOM_NAME: room.name,
			VarNames.INVITEE_USER_ID: users,
			VarNames.INVITER_USER_ID: self.user_id,
			VarNames.HANDLER_NAME: HandlerNames.ROOM,
			VarNames.TIME: get_milliseconds(),
			VarNames.VOLUME: 1,
			VarNames.NOTIFICATIONS: False,
		}
		add_invitee_dumped = encode_message(add_invitee, True)
		for user in users:
			self.raw_publish(add_invitee_dumped, RedisPrefix.generate_user(user))

		invite = {
			VarNames.EVENT: Actions.INVITE_USER,
			VarNames.ROOM_ID: room_id,
			VarNames.INVITEE_USER_ID: users,
			VarNames.INVITER_USER_ID: self.user_id,
			VarNames.HANDLER_NAME: HandlerNames.ROOM,
			VarNames.ROOM_USERS: users_in_room,
			VarNames.TIME: get_milliseconds(),
			VarNames.CB_BY_SENDER: self.id,
			VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID]
		}
		self.publish(invite, room_id, True)

	def respond_ping(self, message):
		self.ws_write(self.message_creator.responde_pong(message[VarNames.JS_MESSAGE_ID]))

	def process_pong_message(self, message):
		self.last_client_ping = message[VarNames.TIME]

	def process_ping_message(self, message):
		def call_check():
			if message[VarNames.TIME] != self.last_client_ping:
				self.close(408, "Ping timeout")
		IOLoop.instance().call_later(settings.PING_CLOSE_SERVER_DELAY, call_check)

	def delete_channel(self, message):
		channel_id = message[VarNames.CHANNEL_ID]
		channel = Channel.objects.get(id=channel_id)
		if channel.creator_id != self.user_id:
			raise ValidationError(f"Only admin can delete this channel. Please ask ${User.objects.get(id=channel.creator_id).username}")
		# if Room.objects.filter(channel_id=channel_id).count() > 0:
		users_id = list(RoomUsers.objects.filter(room__channel_id=channel_id).values_list('user_id', flat=True))
		if len(users_id) > 0:
			raise ValidationError(f"Some users are still in the rooms on this channel Please ask them to leave")
		Room.objects.filter(channel_id=channel_id).update(disabled=True)
		channel.disabled = True
		channel.save()

		message = {
			VarNames.EVENT: Actions.DELETE_CHANNEL,
			VarNames.CHANNEL_ID: channel_id,
			VarNames.HANDLER_NAME: HandlerNames.ROOM,
			VarNames.TIME: get_milliseconds(),
			VarNames.CB_BY_SENDER: self.id,
			VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID]
		}
		self.publish(message, self.channel)

	def delete_room(self, message):
		room_id = message[VarNames.ROOM_ID]
		js_id = message[VarNames.JS_MESSAGE_ID]
		if room_id not in self.channels or room_id == ALL_ROOM_ID:
			raise ValidationError('You are not allowed to exit this room')
		room = Room.objects.get(id=room_id)
		if room.disabled:
			raise ValidationError('Room is already deleted')
		if room.name is None:  # if private then disable
			room.disabled = True
			room.save()
		else:  # if public -> leave the room, delete the link
			RoomUsers.objects.filter(room_id=room.id, user_id=self.user_id).delete()
		ru = list(RoomUsers.objects.filter(room_id=room.id).values_list('user_id', flat=True))
		message = self.message_creator.unsubscribe_direct_message(room_id, js_id, self.id, ru, room.name)
		self.publish(message, room_id, True)

	def edit_message(self, data):
		message = Message.objects.get(id=data[VarNames.MESSAGE_ID])
		validate_edit_message(self.user_id, message)
		message.content = data[VarNames.CONTENT]
		MessageHistory(message=message, content=message.content, giphy=message.giphy).save()
		message.edited_times += 1
		giphy_match = self.isGiphy(data[VarNames.CONTENT])
		if message.content is None:
			Message.objects.filter(id=data[VarNames.MESSAGE_ID]).update(
				deleted=True,
				edited_times=get_milliseconds(),
				content=None
			)
			self.publish(self.message_creator.create_send_message(message, Actions.DELETE_MESSAGE, None, {}), message.room_id)
		elif giphy_match is not None:
			self.edit_message_giphy(giphy_match, message)
		else:
			self.edit_message_edit(data, message)

	def edit_message_giphy(self, giphy_match, message):
		def edit_glyphy(message, giphy):
			Message.objects.filter(id=message.id).update(
				content=message.content,
				symbol=message.symbol,
				giphy=giphy,
				edited_times=get_milliseconds()
			)
			message.giphy = giphy
			self.publish(self.message_creator.create_send_message(message, Actions.EDIT_MESSAGE, None, {}), message.room_id)

		self.search_giphy(message, giphy_match, edit_glyphy)

	def edit_message_edit(self, data, message):
		action = Actions.EDIT_MESSAGE
		message.giphy = None
		tags = data[VarNames.MESSAGE_TAGS]
		files = UploadedFile.objects.filter(id__in=data.get(VarNames.FILES), user_id=self.user_id)
		if files or tags:
			update_symbols(files, tags, message)
		if tags:
			db_tags = MessageMention.objects.filter(message_id=message.id)
			update_or_create = []
			update_or_create_dict = {}
			for db_tag in db_tags:
				if tags.get(db_tag.symbol) and tags.get(db_tag.symbol) != db_tag.user_id:
					update_or_create.append(MessageMention(message_id=message.id, symbol=db_tag.symbol, user_id=tags[db_tag.symbol]))
					update_or_create_dict[db_tag.symbol] = True
			if update_or_create:
				MessageMention.objects.bulk_update(update_or_create)
			create_tags = []
			for (k, v) in tags.items():
				if not update_or_create_dict.get(k):
					create_tags.append(MessageMention(message_id=message.id, symbol=k, user_id=v))
			if create_tags:
				MessageMention.objects.bulk_create(create_tags)

		if files:
			up_files_to_img(files, message.id)
		if message.symbol:  # fetch all, including that we just store
			db_images = Image.objects.filter(message_id=message.id)
			prep_files = MessagesCreator.prepare_img_video(db_images, message.id)
		else:
			prep_files = None
		Message.objects.filter(id=message.id).update(content=message.content, symbol=message.symbol, giphy=None, edited_times=get_milliseconds())
		self.publish(self.message_creator.create_send_message(message, action, prep_files, tags), message.room_id)

	def send_client_new_channel(self, message):
		room_id = message[VarNames.ROOM_ID]
		self.add_channel(room_id)

	def send_client_delete_channel(self, message):
		room_id = message[VarNames.ROOM_ID]
		if message[VarNames.USER_ID] == self.user_id or message[VarNames.ROOM_NAME] is None:
			self.async_redis.unsubscribe((room_id,))
			self.channels.remove(room_id)
			channels = {
				VarNames.EVENT: Actions.DELETE_MY_ROOM,
				VarNames.ROOM_ID: room_id,
				VarNames.HANDLER_NAME: HandlerNames.ROOM,
				VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID],
			}
			self.ws_write(channels)
		else:
			self.ws_write({
				VarNames.EVENT: Actions.USER_LEAVES_ROOM,
				VarNames.ROOM_ID: room_id,
				VarNames.USER_ID: message[VarNames.USER_ID],
				VarNames.ROOM_USERS: message[VarNames.ROOM_USERS],
				VarNames.HANDLER_NAME: HandlerNames.ROOM
			})
		return True

	def process_get_messages_by_ids(self, data):
		"""
		:type data: dict
		"""
		ids = data[VarNames.MESSAGE_IDS]
		room_id = data[VarNames.ROOM_ID]
		messages = Message.objects.filter(room_id=room_id, id__in=ids)
		response = self.message_creator.get_messages(messages, data[VarNames.JS_MESSAGE_ID])
		self.ws_write(response)

	def process_get_messages(self, data):
		"""
		:type data: dict
		"""
		exclude_ids = data[VarNames.EXCLUDE_IDS]
		# this method needs to accept ids of message, because messages on the client can be not-ordered
		# lets say we loaded a message for a thread , so it's single
		# or someone joined from offline and he synced message in the top.
		thread_id = data[VarNames.THREAD_ID]
		room_id = data[VarNames.ROOM_ID]
		if thread_id:
			messages = Message.objects.filter(room_id=room_id, parent_message__id=thread_id)
		else:
			count = int(data.get(VarNames.GET_MESSAGES_COUNT, 10))
			if count > 100:
				raise ValidationError("Can't load that many messages")
			messages = Message.objects.filter(
				Q(room_id=room_id) & Q(parent_message__id=thread_id) & ~Q(id__in=exclude_ids)
			).order_by('-pk')[:count]

		response = self.message_creator.get_messages(messages, data[VarNames.JS_MESSAGE_ID])
		self.ws_write(response)

	def search_messages(self, data):
		offset = data[VarNames.SEARCH_OFFSET] #// room, offset
		messages = Message.objects.filter(
			content__icontains=data[VarNames.SEARCH_STRING],
			room_id=data[VarNames.ROOM_ID] # access permissions is already checked on top level by ROOM_ID
		).order_by('-id')[offset:offset + settings.MESSAGES_PER_SEARCH]

		content =  MessagesCreator.message_models_to_dtos(messages)
		self.ws_write({
			VarNames.CONTENT: content,
			VarNames.JS_MESSAGE_ID: data[VarNames.JS_MESSAGE_ID],
			VarNames.HANDLER_NAME: HandlerNames.NULL
		})

	def show_i_type(self, message):
		self.publish({
			VarNames.ROOM_ID: message[VarNames.ROOM_ID],
			VarNames.USER_ID: self.user_id,
			VarNames.EVENT: Actions.SHOW_I_TYPE,
			VarNames.HANDLER_NAME: HandlerNames.ROOM # because ws-message doesnt exist in p2p
		}, message[VarNames.ROOM_ID])

	def sync_history(self, in_message):
		room_ids = in_message[VarNames.ROOM_IDS]
		message_ids = in_message[VarNames.MESSAGE_IDS]
		if not set(room_ids).issubset(self.channels):
			raise ValidationError("This is not a messages in the room you are in")

		messages = Message.objects.filter(
			Q(room_id__in=room_ids)
			& ~Q(id__in=message_ids)
			& Q(edited_times__gt=get_milliseconds() - in_message[VarNames.LAST_SYNCED])
		)
		content = MessagesCreator.message_models_to_dtos(messages)
		self.ws_write({
			VarNames.CONTENT: content,
			VarNames.JS_MESSAGE_ID: in_message[VarNames.JS_MESSAGE_ID],
			VarNames.HANDLER_NAME: HandlerNames.NULL
		})