def get_toredis_client(): redis_client = Client(host=settings.redis_host, port=settings.redis_port, selected_db=settings.redis_db, password=settings.redis_password) redis_client.connect() return redis_client
def __init__(self, *args, **kwargs): super(StatsWSHandler, self).__init__(*args, **kwargs) self.client = Client(self.application.options.redis_host, self.application.options.redis_port, selected_db=self.application.options.redis_db) self.listen()
def async_client(host=None, port=None, db=None, io_loop=None): """ Returns a new redis client instance. """ options = _get_options(host, port, db) c = Client(**options) return c
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 redis_listen(self): self.redis_client = Client( host=os.environ["REDIS_HOST"], port=int(os.environ["REDIS_PORT"]), selected_db=int(os.environ["REDIS_DB"]), ) yield Task(self.redis_client.subscribe, self.channels.values()) self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe)
def __init__(self, **kwargs): routes = [ (r'/socket', SprintHandler), (r'/(?P<model>task|sprint|user)/(?P<pk>[0-9]+)', UpdateHandler), ] super().__init__(routes, **kwargs) self.subscriber = RedisSubscriber(Client()) self.publisher = Redis() self._key = config('WATERCOOLER_SECRET_KEY') self.signer = signing.TimestampSigner(self._key)
def __init__(self, **kwargs): routes = [ (r'/(?P<sprint>[0-9]+)', SprintHandler), (r'/(?P<model>task|sprint|user)/(?<pk>[0-9]+)', UpdateHandler) ] super().__init__(routes, **kwargs) self.subscriber = RedisSubscriber(Client()) self.publisher = Redis() self._key = os.environ.get('WATERCOOLER_SECRET', 'pTyz1dzMeVUGrb0Su4QXsP984qTlvQRHpFnnlHuHs') self.signer = TimestampSigner(self._key)
class StatsWSHandler(WebSocketHandler): def __init__(self, *args, **kwargs): super(StatsWSHandler, self).__init__(*args, **kwargs) self.client = Client( self.application.options.redis_host, self.application.options.redis_port, selected_db=self.application.options.redis_db ) self.listen() @coroutine def listen(self): client = self.client client.connect() yield Task( client.subscribe, 'stats' ) self.client.listen(self.on_message) def on_message(self, msg): if msg.kind == 'message': self.write_message(str(msg.body)) if msg.kind == 'disconnect': self.close() def on_close(self): client = self.client if client.subscribed: client.unsubscribe('stats') client.disconnect() def check_origin(self, origin): if self.application.debug: return True return super(StatsWSHandler, self).check_origin(origin)
def __init__(self, *args, **kwargs): super(StatsWSHandler, self).__init__(*args, **kwargs) self.client = Client( self.application.options.redis_host, self.application.options.redis_port, selected_db=self.application.options.redis_db ) self.listen()
def __init__(self, command_options): self.options = command_options handlers = self.get_handlers() super(Klassify, self).__init__(handlers, debug=self.debug) self.redis = Client(options.redis_host, options.redis_port, selected_db=options.redis_db)
def __init__(self, **kwargs): routes = [ (r'/socket', SprintHandler), (r'/(?P<model>task|sprint|user)/(?P<pk>[0-9]+)', UpdateHandler), ] super().__init__(routes, **kwargs) self.subscriber = RedisSubscriber(Client()) self.publisher = Redis() self._key = os.environ.get('WATERCOOLER_SECRET', '3121556214dsa654d6asdas') self.signer = TimestampSigner(self._key)
def __init__(self, **kwargs): routes = [ (r'/api/socket', ChatHandler), (r'/chat/(?P<pk>[0-9]+)', UpdateHandler), ] super(ChatApplication, self).__init__(routes, **kwargs) self.subscriber = RedisSubscriber(Client()) self.publisher = Redis() self._key = os.environ.get('TORNADOAPP_SECRET', 'TAfs7y8ajbahvct5r56465avhdsadsg') self.signer = TimestampSigner(self._key)
def __init__(self, **kwargs): routes = [ (r'/socket', SprintHandler), (r'/(?P<model>task|sprint|user)/(?P<pk>[0-9]+)', UpdateHandler), ] super().__init__(routes, **kwargs) self.subscriber = RedisSubscriber(Client()) self.publisher = Redis() # 该密钥必须要与Django应用使用的密钥相一致 self._key = os.environ.get('WATERCOOLER_SECRET', '-i-w&oe-jmzpfc#88i=u+)*9sw21es0x*^om5_6ncb%16k2gtm') self.signer = TimestampSigner(self._key)
def __init__(self, **kwargs): routes = [ (r'/socket', SprintHandler), (r'/(?P<model>task|sprint|user)/(?P<pk>[0-9]+)', UpdateHandler), ] super().__init__(routes, **kwargs) self.subscriber = RedisSubscriber(Client()) self.publisher = Redis() self._key = os.environ.get( 'WATERCOOLER_SECRET', ')zu-07tfvq5&@f^k26f&c58w+w$q=r#ttx!j6pku(-lj6d3jtv') self.signer = TimestampSigner(self._key)
def __init__(self, **kwargs): route = [ (r'/(?P<sprint>[0-9]+)', SprintHandler), (r'/(?P<model>task|sprint|user)/(?P<pk>[0-9]+)', UpdateHandler), ] super().__init__(route, **kwargs) # self.subscriptions = defaultdict(list) self.subscriber = RedisSubscriber(Client()) self.publisher = Redis() self._key = os.environ.get( 'WATERCOOLER_SECRET', 'pTyz1dzMeVUGrb0Su4QXsP984qTlvQRHpFnnlHuH') ###随机设置一个共享密令 self.signer = TimestampSigner(self._key)
def __init__(self, *args, **kwargs): self.closed_channels = None super(MessagesHandler, self).__init__() self.webrtc_ids = {} self.id = None # child init self.sex = None self.last_client_ping = 0 self.sender_name = None 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) 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.SEND_MESSAGE: self.process_send_message, Actions.CREATE_DIRECT_CHANNEL: self.create_user_channel, Actions.DELETE_ROOM: self.delete_channel, Actions.EDIT_MESSAGE: self.edit_message, Actions.CREATE_ROOM_CHANNEL: self.create_new_room, Actions.INVITE_USER: self.invite_user, Actions.PING: self.respond_ping, Actions.PONG: self.process_pong_message, } # 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_DIRECT_CHANNEL: self.send_client_new_channel, Actions.CREATE_ROOM_CHANNEL: self.send_client_new_channel, Actions.DELETE_ROOM: self.send_client_delete_channel, Actions.INVITE_USER: self.send_client_new_channel, Actions.PING: self.process_ping_message, }
class MessageHandler(WebSocketHandler): def __init__(self, *args, **kwargs): super(MessageHandler, self).__init__(*args, **kwargs) self.redis = Client() self.redis.connect() def get_current_user(self): user = self.get_secure_cookie("user") if user is None: return '' else: return user.strip('" ') def on_message(self, msg): msginfo = loads(msg) #listens for handshake from page if "user:" in msginfo['msg']: self.channel = msginfo['msg'].split(':')[1] #need to split the rest off to new func so it can be asynchronous self.listen() # Decorator turns the function into an asynchronous generator object @tornado.gen.engine def listen(self): #runs task given, with the yield required to get returned value #equivalent of callback/wait pairing from tornado.gen yield tornado.gen.Task(self.redis.subscribe, self.channel) if not self.redis.subscribed: self.write_message('ERROR IN SUBSCRIPTION') #listen from tornadoredis makes the listen object asynchronous #if using standard redis lib, it blocks while listening self.redis.listen(self.callback) # Try and fight race condition by loading from redis after listen # started need to use std redis lib because tornadoredis is in # subscribed state oldmessages = r_server.lrange(self.channel + ':messages', 0, -1) if oldmessages is not None: for message in oldmessages: self.write_message(message) def callback(self, msg): if msg.kind == 'message': self.write_message(str(msg.body)) @tornado.gen.engine def on_close(self): yield tornado.gen.Task(self.redis.unsubscribe, self.channel) self.redis.disconnect()
class MessageHandler(WebSocketHandler): def __init__(self, *args, **kwargs): super(MessageHandler, self).__init__(*args, **kwargs) self.redis = Client() self.redis.connect() def get_current_user(self): user = self.get_secure_cookie("user") if user == None: return '' else: return user.strip('" ') def on_message(self, msg): msginfo = loads(msg) #listens for handshake from page if "user:" in msginfo['msg']: self.channel = msginfo['msg'].split(':')[1] #need to split the rest off to new func so it can be asynchronous self.listen() #decorator turns the function into an asynchronous generator object @tornado.gen.engine def listen(self): #runs task given, with the yield required to get returned value #equivalent of callback/wait pairing from tornado.gen yield tornado.gen.Task(self.redis.subscribe, self.channel) if not self.redis.subscribed: self.write_message('ERROR IN SUBSCRIPTION') #listen from tornadoredis makes the listen object asynchronous #if using standard redis lib, it blocks while listening self.redis.listen(self.callback) #try and fight race condition by loading from redis after listen started #need to use std redis lib because tornadoredis is in subscribed state oldmessages = r_server.lrange(self.channel + ':messages', 0, -1) if oldmessages != None: for message in oldmessages: self.write_message(message) def callback(self, msg): if msg.kind == 'message': self.write_message(str(msg.body)) @tornado.gen.engine def on_close(self): yield tornado.gen.Task(self.redis.unsubscribe, self.channel) self.redis.disconnect()
class MessagesHandler(MessagesCreator): def __init__(self, *args, **kwargs): self.closed_channels = None super(MessagesHandler, self).__init__() self.webrtc_ids = {} self.id = None # child init self.sex = None self.last_client_ping = 0 self.sender_name = None 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) 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.SEND_MESSAGE: self.process_send_message, Actions.CREATE_DIRECT_CHANNEL: self.create_user_channel, Actions.DELETE_ROOM: self.delete_channel, Actions.EDIT_MESSAGE: self.edit_message, Actions.CREATE_ROOM_CHANNEL: self.create_new_room, Actions.INVITE_USER: self.invite_user, Actions.PING: self.respond_ping, Actions.PONG: self.process_pong_message, } # 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_DIRECT_CHANNEL: self.send_client_new_channel, Actions.CREATE_ROOM_CHANNEL: self.send_client_new_channel, Actions.DELETE_ROOM: self.send_client_delete_channel, Actions.INVITE_USER: 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( RedisPrefix.DEFAULT_CHANNEL) 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 connected(self): raise NotImplemented @connected.setter def connected(self, value): raise NotImplemented @property def http_client(self): raise NotImplemented @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 @engine def add_channel(self, channel): self.channels.append(channel) yield Task(self.async_redis.subscribe, (channel, )) def get_online_from_redis(self, channel): return self.get_online_and_status_from_redis(channel)[1] def get_online_and_status_from_redis(self, channel): """ :rtype : (bool, list) """ online = self.sync_redis.ssmembers(channel) self.logger.debug('!! channel %s redis online: %s', channel, 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 # : char specified in cookies_middleware.py.create_id 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 add_online_user(self, room_id, is_online, online): """ adds to redis online_users = { connection_hash1 = stored_redis_user1, connection_hash_2 = stored_redis_user2 } :return: if user is online """ if is_online: # Send user names to self online_user_names_mes = self.room_online(online, Actions.REFRESH_USER, room_id) self.logger.info('!! Second tab, retrieving online for self') self.ws_write(online_user_names_mes) else: # if a new tab has been opened online.append(self.user_id) online_user_names_mes = self.room_online(online, Actions.LOGIN, room_id) self.logger.info('!! First tab, sending refresh online for all') self.publish(online_user_names_mes, room_id) def get_is_online(self, room_id): self.async_redis_publisher.sadd(room_id, self.id) # since we add user to online first, latest trigger will always show correct online return self.get_online_and_status_from_redis(room_id) def publish(self, message, channel, parsable=False): jsoned_mess = encode_message(message, parsable) 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') @asynchronous 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='')) self.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(channel) 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)) @asynchronous 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) r = HTTPRequest(FIREBASE_URL, method="POST", headers=headers, body=body) self.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): files = UploadedFile.objects.filter(id__in=message.get( VarNames.FILES), user_id=self.user_id) symbol = get_max_key(files) channel = message[VarNames.CHANNEL] js_id = message[VarNames.JS_MESSAGE_ID] message_db = Message(sender_id=self.user_id, content=message[VarNames.CONTENT], symbol=symbol, giphy=giphy, room_id=channel) res_files = [] do_db(message_db.save) if files: images = up_files_to_img(files, message_db.id) res_files = MessagesCreator.prepare_img_video( images, message_db.id) prepared_message = self.create_send_message( message_db, Actions.PRINT_MESSAGE, res_files, 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 create_new_room(self, message): room_name = message[VarNames.ROOM_NAME] if not room_name or len(room_name) > 16: raise ValidationError('Incorrect room name "{}"'.format(room_name)) room = Room(name=room_name) do_db(room.save) RoomUsers(room_id=room.id, user_id=self.user_id).save() subscribe_message = self.subscribe_room_channel_message( room.id, room_name) self.publish(subscribe_message, self.channel, True) def invite_user(self, message): room_id = message[VarNames.ROOM_ID] user_id = message[VarNames.USER_ID] room = get_or_create_room(self.channels, room_id, user_id) users_in_room = { user.id: RedisPrefix.set_js_user_structure(user.username, user.sex) for user in room.users.all() } self.publish( self.add_user_to_room(room_id, user_id, users_in_room[user_id]), room_id) subscribe_message = self.invite_room_channel_message( room_id, user_id, room.name, users_in_room) self.publish(subscribe_message, RedisPrefix.generate_user(user_id), True) def respond_ping(self, message): self.ws_write(self.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 create_user_channel(self, message): user_id = message[VarNames.USER_ID] room_id = create_room(self.user_id, user_id) subscribe_message = self.subscribe_direct_channel_message( room_id, user_id, self.user_id != user_id) self.publish(subscribe_message, self.channel, True) other_channel = RedisPrefix.generate_user(user_id) if self.channel != other_channel: self.publish(subscribe_message, other_channel, True) def delete_channel(self, message): room_id = message[VarNames.ROOM_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 = do_db(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 else: # if public -> leave the room, delete the link RoomUsers.objects.filter(room_id=room.id, user_id=self.user_id).delete() online = self.get_online_from_redis(room_id) online.remove(self.user_id) self.publish(self.room_online(online, Actions.LOGOUT, room_id), room_id) room.save() message = self.unsubscribe_direct_message(room_id) self.publish(message, room_id, True) def edit_message(self, data): js_id = data[VarNames.JS_MESSAGE_ID] message = do_db(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=message.edited_times, content=None) self.publish( self.create_send_message(message, Actions.DELETE_MESSAGE, None, js_id), message.room_id) elif giphy_match is not None: self.edit_message_giphy(giphy_match, message, js_id) else: self.edit_message_edit(data, message, js_id) def edit_message_giphy(self, giphy_match, message, js_id): def edit_glyphy(message, giphy): do_db(Message.objects.filter(id=message.id).update, content=message.content, symbol=message.symbol, giphy=giphy, edited_times=message.edited_times) message.giphy = giphy self.publish( self.create_send_message(message, Actions.EDIT_MESSAGE, None, js_id), message.room_id) self.search_giphy(message, giphy_match, edit_glyphy) def edit_message_edit(self, data, message, js_id): action = Actions.EDIT_MESSAGE message.giphy = None files = UploadedFile.objects.filter(id__in=data.get(VarNames.FILES), user_id=self.user_id) if files: update_symbols(files, message) 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=message.edited_times) self.publish( self.create_send_message(message, action, prep_files, js_id), message.room_id) def send_client_new_channel(self, message): room_id = message[VarNames.ROOM_ID] self.add_channel(room_id) is_online, online = self.get_is_online(room_id=room_id) self.add_online_user(room_id, is_online, online) def send_client_delete_channel(self, message): room_id = message[VarNames.ROOM_ID] self.async_redis.unsubscribe((room_id, )) self.async_redis_publisher.hdel(room_id, self.id) self.channels.remove(room_id) def process_get_messages(self, data): """ :type data: dict """ header_id = data.get(VarNames.GET_MESSAGES_HEADER_ID, None) count = int(data.get(VarNames.GET_MESSAGES_COUNT, 10)) room_id = data[VarNames.CHANNEL] self.logger.info('!! Fetching %d messages starting from %s', count, header_id) if header_id is None: messages = Message.objects.filter( room_id=room_id).order_by('-pk')[:count] else: messages = Message.objects.filter( Q(id__lt=header_id), Q(room_id=room_id)).order_by('-pk')[:count] imv = do_db(get_message_images_videos, messages) response = self.get_messages(messages, room_id, imv, MessagesCreator.prepare_img_video) self.ws_write(response)
class ChatHandler(WebSocketHandler): def get_chat_user(self): return self.db.query( ChatUser, User, AnyChat, ).join( User, ChatUser.user_id == User.id, ).join( AnyChat, ChatUser.chat_id == AnyChat.id, ).filter(and_( ChatUser.user_id == self.user_id, ChatUser.chat_id == self.chat_id, )).one() def set_typing(self, is_typing): command = redis.sadd if is_typing else redis.srem typing_key = "chat:%s:typing" % self.chat_id if command(typing_key, self.user_number): redis.publish(self.channels["typing"], json.dumps({ "typing": list(int(_) for _ in redis.smembers(typing_key)), })) def check_origin(self, origin): return origin_regex.match(origin) is not None def prepare(self): self.id = str(uuid4()) self.joined = False try: self.session_id = self.cookies["session"].value self.chat_id = int(self.path_args[0]) self.user_id = int(redis.get("session:%s" % self.session_id)) except (KeyError, TypeError, ValueError): self.send_error(400) return self.db = sm() try: self.chat_user, self.user, self.chat = self.get_chat_user() except NoResultFound: self.send_error(404) return # Remember the user number so typing notifications can refer to it # without reopening the database session. self.user_number = self.chat_user.number self.user.last_online = datetime.now() self.user.last_ip = self.request.headers["X-Forwarded-For"] if self.user.group == "banned": self.send_error(403) return try: authorize_joining(redis, self.db, self) except (UnauthorizedException, BannedException, TooManyPeopleException): self.send_error(403) return def open(self, chat_id): sockets.add(self) redis.sadd("chat:%s:sockets:%s" % (self.chat_id, self.session_id), self.id) print "socket opened:", self.id, self.chat.url, self.user.username try: kick_check(redis, self) except KickedException: self.write_message(json.dumps({"exit": "kick"})) self.close() return # Subscribe self.channels = { "chat": "channel:%s" % self.chat_id, "user": "******" % (self.chat_id, self.user_id), "typing": "channel:%s:typing" % self.chat_id, } if self.chat.type == "pm": self.channels["pm"] = "channel:pm:%s" % self.user_id self.redis_listen() # Send backlog. try: after = int(self.request.query_arguments["after"][0]) except (KeyError, IndexError, ValueError): after = 0 messages = redis.zrangebyscore("chat:%s" % self.chat_id, "(%s" % after, "+inf") self.write_message(json.dumps({ "chat": self.chat.to_dict(), "messages": [json.loads(_) for _ in messages], })) join_message_sent = join(redis, self.db, self) self.joined = True # Send userlist if nothing was sent by join(). if not join_message_sent: self.write_message(json.dumps({"users": get_userlist(self.db, redis, self.chat)})) self.db.commit() def on_message(self, message): print "message:", message if message in ("typing", "stopped_typing"): self.set_typing(message == "typing") def on_close(self): # Unsubscribe here and let the exit callback handle disconnecting. if hasattr(self, "redis_client"): self.redis_client.unsubscribe(self.redis_client.subscribed) if self.joined and disconnect(redis, self.chat_id, self.id): try: send_quit_message(self.db, redis, *self.get_chat_user()) except NoResultFound: send_userlist(self.db, redis, self.chat) self.db.commit() self.set_typing(False) print "socket closed:", self.id redis.srem("chat:%s:sockets:%s" % (self.chat_id, self.session_id), self.id) sockets.remove(self) def finish(self, *args, **kwargs): if hasattr(self, "db"): self.db.close() del self.db super(ChatHandler, self).finish(*args, **kwargs) @engine def redis_listen(self): self.redis_client = Client( host=os.environ["REDIS_HOST"], port=int(os.environ["REDIS_PORT"]), selected_db=int(os.environ["REDIS_DB"]), ) yield Task(self.redis_client.subscribe, self.channels.values()) self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe) def on_redis_message(self, message): print "redis message:", message if message.kind != "message": return self.write_message(message.body) if message.channel == self.channels["user"]: data = json.loads(message.body) if "exit" in data: self.joined = False self.close() def on_redis_unsubscribe(self, callback): self.redis_client.disconnect()
def redis_listen(self): self.redis_client = Client(unix_socket_path=config.get("app:main", "cherubplay.socket_pubsub")) yield Task(self.redis_client.subscribe, self.redis_channels) self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) info = self.parse_redis_parameters() db_number = info.pop('db', 0) self.publisher = Redis(db=db_number, **info) self.subscriber = RedisSubscriber(Client(selected_db=db_number, **info))
class ChatHandler(WebSocketHandler): user = None chat = None chat_user = None socket_id = None redis_channels = () ignore_next_message = False redis_client = None def check_origin(self, origin): return origin == config.get("app:main", "cherubplay.socket_origin") def open(self, chat_url): sockets.add(self) with db_session() as db: self.user = get_user(db, self.cookies) if self.user is None: self.close() return self.chat = get_chat(db, chat_url) if self.chat is None: self.close() return self.chat_user = get_chat_user(db, self.chat.id, self.user.id) if self.chat_user is None: self.close() return self.socket_id = str(uuid4()) # Fire online message, but only if this is the only tab we have open. online_handles = online_user_store.online_handles(self.chat) if self.chat_user.handle not in online_handles: publish_client.publish(pubsub_channel(self.chat), json.dumps({ "action": "online", "handle": self.chat_user.handle, })) # See if anyone else is online. online_handles_except_self = online_handles - {self.chat_user.handle} if len(online_handles_except_self) == 1: self.write_message({"action": "online", "handle": next(iter(online_handles_except_self))}) elif len(online_handles_except_self) >= 1: self.write_message({ "action": "online_list", "handles": sorted(list(online_handles)), }) online_user_store.connect(self.chat_user, self.socket_id) self.redis_channels = ( pubsub_channel(self.chat), pubsub_channel(self.user), pubsub_channel(self.chat_user), ) self.redis_listen() # Send the backlog if necessary. if "after" in self.request.query_arguments: try: after = int(self.request.query_arguments["after"][0]) except ValueError: return for message in ( db.query(Message) .options(joinedload(Message.chat_user)) .filter(and_(Message.chat_id == self.chat.id, Message.id > after)) ): self.write_message({ "action": "message", "message": { "id": message.id, "type": message.type.value, "colour": message.colour, "symbol": message.symbol_character, "name": message.chat_user.name if message.chat_user else None, "text": message.text.as_plain_text(), "html": message.text.as_html(), } }) def on_message(self, message_string): message = json.loads(message_string) if message["action"] in ("typing", "stopped_typing"): publish_client.publish(pubsub_channel(self.chat), json.dumps({ "action": message["action"], "handle": self.chat_user.handle, })) # Ignore our own typing messages. self.ignore_next_message = True def on_close(self): # Unsubscribe here and let the exit callback handle disconnecting. if self.redis_client: self.redis_client.unsubscribe(self.redis_channels) online_user_store.disconnect(self.chat, self.socket_id) # Fire offline message, but only if we don't have any other tabs open. if self.chat_user.handle not in online_user_store.online_handles(self.chat): publish_client.publish(pubsub_channel(self.chat), json.dumps({ "action": "offline", "handle": self.chat_user.handle, })) sockets.remove(self) @engine def redis_listen(self): self.redis_client = Client(unix_socket_path=config.get("app:main", "cherubplay.socket_pubsub")) yield Task(self.redis_client.subscribe, self.redis_channels) self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe) def on_redis_message(self, message): if message.kind == "message": if message.body == "kicked": self.write_message("{\"action\":\"kicked\"}") self.close() elif not self.ignore_next_message: self.write_message(message.body) else: self.ignore_next_message = False def on_redis_unsubscribe(self, callback): self.redis_client.disconnect()
def __init__(self): worker_logger.info('Initialized Redis Subscriber instance') self._client = Client(host=REDIS_HOST, port=int(REDIS_PORT)) self._subscribers = {} self._listen()
def connect_redis(): rds = Client(host=settings.REDIS['Master']['host'], port=settings.REDIS['Master']['port'], selected_db=settings.REDIS['Master']['db']) rds.connect() return rds
def __init__(self, *args, **kwargs): super(MessageHandler, self).__init__(*args, **kwargs) self.redis = Client() self.redis.connect()
def redis_listen(self): self.redis_client = Client(unix_socket_path=config.get("app:main", "cherubplay.socket_pubsub")) yield Task(self.redis_client.subscribe, "chat:"+str(self.chat.id)) self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe)
class MessagesHandler(MessagesCreator): def __init__(self, *args, **kwargs): self.closed_channels = 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.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 } # 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 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?") files = UploadedFile.objects.filter(id__in=message.get( VarNames.FILES), user_id=self.user_id) symbol = get_max_key(files) channel = message[VarNames.ROOM_ID] js_id = message[VarNames.JS_MESSAGE_ID] message_db = Message(sender_id=self.user_id, content=message[VarNames.CONTENT], symbol=symbol, giphy=giphy, room_id=channel) message_db.time -= message[VarNames.TIME_DIFF] res_files = [] message_db.save() if files: images = up_files_to_img(files, message_db.id) res_files = MessagesCreator.prepare_img_video( images, message_db.id) prepared_message = self.create_send_message( message_db, Actions.PRINT_MESSAGE, res_files, ) 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.CHANNELS, 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.CHANNELS, 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.CHANNELS, 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.CHANNELS, 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.CHANNELS, 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], logs=message[UserSettingsVarNames.LOGS], send_logs=message[UserSettingsVarNames.SEND_LOGS], theme=message[UserSettingsVarNames.THEME], ) self.publish( self.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.set_user_profile(in_message[VarNames.JS_MESSAGE_ID], message), self.channel) if userprofile.sex_str != sex or userprofile.username != un: self.publish(self.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.CHANNELS, 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.CHANNELS, 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.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.CHANNELS, 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.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=message.edited_times, content=None) self.publish( self.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=message.edited_times) message.giphy = giphy self.publish( self.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 files = UploadedFile.objects.filter(id__in=data.get(VarNames.FILES), user_id=self.user_id) if files: update_symbols(files, message) 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=message.edited_times) self.publish(self.create_send_message(message, action, prep_files), 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.CHANNELS, 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.CHANNELS }) return True def process_get_messages(self, data): """ :type data: dict """ header_id = data.get(VarNames.GET_MESSAGES_HEADER_ID, None) count = int(data.get(VarNames.GET_MESSAGES_COUNT, 10)) room_id = data[VarNames.ROOM_ID] self.logger.info('!! Fetching %d messages starting from %s', count, header_id) if header_id is None: messages = Message.objects.filter( room_id=room_id).order_by('-pk')[:count] else: messages = Message.objects.filter( Q(id__lt=header_id), Q(room_id=room_id)).order_by('-pk')[:count] imv = get_message_images_videos(messages) response = self.get_messages(messages, room_id, imv, MessagesCreator.prepare_img_video, data[VarNames.JS_MESSAGE_ID]) self.ws_write(response)
class ChatHandler(WebSocketHandler): def check_origin(self, origin): return True def open(self, chat_url): sockets.add(self) print chat_url self.user = get_user(self.cookies) if self.user is None: self.close() return self.chat = get_chat(chat_url) if self.chat is None: self.close() return self.chat_user = get_chat_user(self.chat.id, self.user.id) if self.chat_user is None: self.close() return self.socket_id = str(uuid4()) # Fire online message, but only if this is the only tab we have open. online_symbols = set(int(_) for _ in publish_client.hvals("online:"+str(self.chat.id))) if self.chat_user.symbol not in online_symbols: publish_client.publish("chat:"+str(self.chat.id), json.dumps({ "action": "online", "symbol": symbols[self.chat_user.symbol], })) # See if the other person is online. for symbol in online_symbols: if symbol == self.chat_user.symbol: continue self.write_message({ "action": "online", "symbol": symbols[symbol], }) publish_client.hset("online:"+str(self.chat.id), self.socket_id, self.chat_user.symbol) self.redis_listen() self.ignore_next_message = False # Send the backlog if necessary. if "after" in self.request.query_arguments: print "after" try: after = int(self.request.query_arguments["after"][0]) except ValueError: return Session = sm() for message in Session.query(Message).filter(and_( Message.chat_id == self.chat.id, Message.id > after, )): print message self.write_message({ "action": "message", "message": { "id": message.id, "type": message.type, "colour": message.colour, "symbol": symbols[message.symbol], "text": message.text, } }) Session.commit() def on_message(self, message_string): message = json.loads(message_string) if message["action"] in ("typing", "stopped_typing"): publish_client.publish("chat:"+str(self.chat.id), json.dumps({ "action": message["action"], "symbol": symbols[self.chat_user.symbol], })) # Ignore our own typing messages. self.ignore_next_message = True print message def on_close(self): # Unsubscribe here and let the exit callback handle disconnecting. self.redis_client.unsubscribe("chat:"+str(self.chat.id)) publish_client.hdel("online:"+str(self.chat.id), self.socket_id) # Fire offline message, but only if we don't have any other tabs open. if str(self.chat_user.symbol) not in publish_client.hvals("online:"+str(self.chat.id)): publish_client.publish("chat:"+str(self.chat.id), json.dumps({ "action": "offline", "symbol": symbols[self.chat_user.symbol], })) sockets.remove(self) @engine def redis_listen(self): self.redis_client = Client(unix_socket_path=config.get("app:main", "cherubplay.socket_pubsub")) yield Task(self.redis_client.subscribe, "chat:"+str(self.chat.id)) self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe) def on_redis_message(self, message): if message.kind=="message": if not self.ignore_next_message: self.write_message(message.body) print "redis message:", message.body else: self.ignore_next_message = False def on_redis_unsubscribe(self, callback): self.redis_client.disconnect()
class RedisSubscriber: _instance = None _lock = Lock() def __init__(self): worker_logger.info('Initialized Redis Subscriber instance') self._client = Client(host=REDIS_HOST, port=int(REDIS_PORT)) self._subscribers = {} self._listen() def __del__(self): worker_logger.info('Redis Subscriber was successfully destroyed') @classmethod def instance(cls): with cls._lock: if cls._instance is None: cls._instance = RedisSubscriber() return cls._instance def subscribe(self, channel_key, callback): worker_logger.info( 'Subscribing channel - %s , with the callback - %s' % (channel_key, callback.__name__)) self._subscribers.update({channel_key: callback}) def unsubscribe(self, channel_key): worker_logger.info("Unsubscribing from channel - %s" % channel_key) if channel_key in self._subscribers: del self._subscribers[channel_key] @tornado.gen.engine def _listen(self): self._client.connect() yield tornado.gen.Task( self._client.psubscribe, "__keyspace*:%s" % (REDIS_GENERAL_CHANNEL % '*')) self._client.listen(self._message_handler) def _message_handler(self, message): if message.kind == 'pmessage': channel_key = re.search(REDIS_GENERAL_CHANNEL % '(.*)', message.channel) channel_callbacks = [] if channel_key is not None: channel_key = channel_key.group(0) if channel_key in self._subscribers: channel_callbacks[0] = self._subscribers.get(channel_key) else: for subscriber_channel_key in self._subscribers.keys(): re_channel_key = subscriber_channel_key.replace( '*', '.*') if re.match(re_channel_key, channel_key): channel_callbacks.append( self._subscribers.get(subscriber_channel_key)) if len(channel_callbacks): for channel_callback in channel_callbacks: try: tornado.ioloop.IOLoop.instance().call_later( callback=channel_callback, delay=0, message=message) except Exception as e: worker_logger.warn('Failed to run channel callback') worker_logger.exception(e)