def set_info(): # Validation: We must be allowed to set the topic. if not g.chat_user.can("set_info"): abort(403) description = request.form["description"].strip()[:Message.MAX_LENGTH] rules = request.form["rules"].strip()[:Message.MAX_LENGTH] # If it hasn't changed, don't bother sending a message about it. if (description == g.chat.description and rules == g.chat.rules): return "", 204 g.chat.description = description g.chat.rules = rules send_message( g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, name=g.chat_user.name, type="chat_meta", text="%s [%s] edited the chat information." % ( g.chat_user.name, g.chat_user.acronym, ), ), g.user_list) return "", 204
def set_info(): # Validation: We must be allowed to set the topic. if not g.chat_user.can("set_info"): abort(403) description = request.form["description"].strip()[:Message.MAX_LENGTH] rules = request.form["rules"].strip()[:Message.MAX_LENGTH] # If it hasn't changed, don't bother sending a message about it. if (description == g.chat.description and rules == g.chat.rules): return "", 204 g.chat.description = description g.chat.rules = rules send_message(g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, name=g.chat_user.name, type="chat_meta", text="%s [%s] edited the chat information." % ( g.chat_user.name, g.chat_user.acronym, ), )) return "", 204
def set_topic(): # Validation: We must be allowed to set the topic. if not g.chat_user.can("set_topic"): abort(403) topic = request.form["topic"].strip()[:500] # If it hasn't changed, don't bother sending a message about it. if topic == g.chat.topic: return "", 204 g.chat.topic = topic if topic == "": send_message(g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, name=g.chat_user.name, type="chat_meta", text="%s [%s] removed the conversation topic." % ( g.chat_user.name, g.chat_user.acronym, ), )) else: send_message(g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, name=g.chat_user.name, type="chat_meta", text="%s [%s] changed the topic to \"%s\"" % ( g.chat_user.name, g.chat_user.acronym, topic, ), )) return "", 204
def reap(): redis = reap.redis with session_scope() as db: current_time = int(time.time()) disconnected_users = set() # Long poll sessions. for dead in redis.zrangebyscore("chats_alive", 0, current_time): logger.info("Reaping %s" % dead) chat_id, session_id = dead.split('/') user_id = redis.hget("chat:%s:online" % chat_id, session_id) disconnected = disconnect(redis, chat_id, session_id) # Only send a timeout message if they were already online. if not disconnected: logger.info("Not sending timeout message.") continue disconnected_users.add((chat_id, user_id, False)) # Sockets. for dead in redis.zrangebyscore("sockets_alive", 0, current_time): logger.info("Reaping %s" % dead) chat_id, session_id, socket_id = dead.split('/') user_id = redis.hget("chat:%s:online" % chat_id, socket_id) disconnected = disconnect(redis, chat_id, socket_id) redis.srem("chat:%s:sockets:%s" % (chat_id, session_id), socket_id) redis.zrem("sockets_alive", "%s/%s/%s" % (chat_id, session_id, socket_id)) # Only send a timeout message if they were already online. if not disconnected: logger.info("Not sending timeout message.") continue disconnected_users.add((chat_id, user_id, True)) for chat_id, user_id, reaped_socket in disconnected_users: try: dead_chat_user = db.query(ChatUser).filter(and_( ChatUser.user_id == user_id, ChatUser.chat_id == chat_id, )).options(joinedload(ChatUser.chat), joinedload(ChatUser.user)).one() except NoResultFound: logger.error("Unable to find ChatUser (chat %s, user %s)." % (chat_id, user_id)) continue if reaped_socket: typing_key = "chat:%s:typing" % chat_id if redis.srem(typing_key, dead_chat_user.number): redis.publish("channel:%s:typing" % chat_id, json.dumps({ "typing": list(int(_) for _ in redis.smembers(typing_key)), })) if dead_chat_user.computed_group == "silent" or dead_chat_user.chat.type in ("pm", "roulette"): send_userlist(db, redis, dead_chat_user.chat) else: send_message(db, redis, Message( chat_id=chat_id, user_id=dead_chat_user.user_id, type="timeout", name=dead_chat_user.name, text="%s's connection timed out." % dead_chat_user.name, )) logger.info("Sent timeout message for ChatUser (chat %s, user %s)." % (chat_id, user_id))
def reap_chat(chat_id): user_list = UserListStore(NewparpRedis(connection_pool=redis_chat_pool), chat_id) old_user_ids = user_list.user_ids_online() if not old_user_ids: return with session_scope() as db: chat = db.query(Chat).filter(Chat.id == chat_id).one() chat_users = { _.user_id: _ for _ in db.query(ChatUser).filter(and_( ChatUser.chat_id == chat_id, ChatUser.user_id.in_(old_user_ids), )) } for socket_id, user_id in user_list.inconsistent_entries(): chat_user = chat_users[user_id] dead = user_list.socket_disconnect(socket_id, chat_user.number) if dead: logger.debug("dead: %s" % chat_user) # TODO optimise this when reaping several people at once? if chat_user.computed_group == "silent" or chat.type in ("pm", "roulette"): send_userlist(user_list, db, chat) else: send_message(db, reap_chat.redis, Message( chat=chat, user_id=chat_user.user_id, type="timeout", name=chat_user.name, text="%s's connection timed out." % chat_user.name, ), user_list)
def send(): if g.chat_user.computed_group == "silent": abort(403) if "text" not in request.form: abort(400) text = request.form["text"].strip()[:Message.MAX_LENGTH] if text == "": abort(400) message_type = request.form.get("type", "ic") if message_type not in ("ic", "ooc", "me"): message_type = "ic" # Set color, name and acronym based on a saved character. character = None if "character_id" in request.form: try: character = g.db.query(Character).filter( and_( Character.id == request.form["character_id"], Character.user_id == g.user.id, )).order_by(Character.title).one() except NoResultFound: pass # Clear typing status so the front end doesn't have to. if g.user_list.user_stop_typing(g.chat_user.number): g.redis.publish( "channel:%s:typing" % g.chat.id, json.dumps({ "typing": g.user_list.user_numbers_typing(), })) g.chat_user.draft = "" send_message( g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, type=message_type, color=character.color if character is not None else g.chat_user.color, acronym=character.acronym if character is not None else g.chat_user.acronym, name=character.name if character is not None else g.chat_user.name, text=text, ), g.user_list) return "", 204
def unban(chat, pm_user, url, fmt): if chat.type != "group": abort(404) try: own_chat_user = g.db.query(ChatUser).filter( and_( ChatUser.chat_id == chat.id, ChatUser.user_id == g.user.id, )).one() except NoResultFound: abort(404) if not own_chat_user.can("ban"): abort(403) try: unban_chat_user = g.db.query(ChatUser).filter( and_( ChatUser.chat_id == chat.id, ChatUser.number == int(request.form["number"]), )).one() except (NoResultFound, ValueError): abort(404) try: ban = g.db.query(Ban).filter( and_(Ban.chat_id == chat.id, Ban.user_id == unban_chat_user.user_id)).one() except NoResultFound: abort(404) g.db.delete(ban) send_message( g.db, g.redis, Message( chat_id=chat.id, user_id=unban_chat_user.user_id, type="user_action", name=own_chat_user.name, text="%s [%s] unbanned %s [%s] from the chat." % ( own_chat_user.name, own_chat_user.acronym, unban_chat_user.name, unban_chat_user.acronym, ), ), g.user_list) if "Referer" in request.headers: return redirect(request.headers["Referer"]) return redirect(url_for("rp_chat_users", url=url))
def send(): if g.chat_user.computed_group == "silent": abort(403) if "text" not in request.form: abort(400) text = request.form["text"].strip()[:Message.MAX_LENGTH] if text == "": abort(400) message_type = request.form.get("type", "ic") if message_type not in ("ic", "ooc", "me"): message_type = "ic" # Set color, name and acronym based on a saved character. character = None if "character_id" in request.form: try: character = g.db.query(Character).filter(and_( Character.id == request.form["character_id"], Character.user_id == g.user.id, )).order_by(Character.title).one() except NoResultFound: pass # Clear typing status so the front end doesn't have to. if g.redis.scard("chat:%s:sockets:%s" % (g.chat.id, g.session_id)): typing_key = "chat:%s:typing" % g.chat.id if g.redis.srem(typing_key, g.chat_user.number): g.redis.publish("channel:%s:typing" % g.chat.id, json.dumps({ "typing": list(int(_) for _ in g.redis.smembers(typing_key)), })) g.chat_user.draft = "" send_message(g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, type=message_type, color=character.color if character is not None else g.chat_user.color, acronym=character.acronym if character is not None else g.chat_user.acronym, name=character.name if character is not None else g.chat_user.name, text=text, )) return "", 204
def save_from_character(): try: character = g.db.query(Character).filter( and_( Character.id == request.form["character_id"], Character.user_id == g.user.id, )).order_by(Character.title).one() except NoResultFound: abort(404) # Remember old values so we can check if they've changed later. old_name = g.chat_user.name old_acronym = g.chat_user.acronym # Send a message if name, acronym or color has changed. changed = (g.chat_user.name != character.name or g.chat_user.acronym != character.acronym or g.chat_user.color != character.color) g.chat_user.name = character.name g.chat_user.acronym = character.acronym g.chat_user.color = character.color g.chat_user.quirk_prefix = character.quirk_prefix g.chat_user.quirk_suffix = character.quirk_suffix g.chat_user.case = character.case g.chat_user.replacements = character.replacements g.chat_user.regexes = character.regexes if changed: if g.chat_user.computed_group == "silent": send_userlist(g.user_list, g.db, g.chat) else: send_message( g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, type="user_info", name=g.chat_user.name, text="%s [%s] is now %s [%s]." % ( old_name, old_acronym, g.chat_user.name, g.chat_user.acronym, ), ), g.user_list) return jsonify(g.chat_user.to_dict(include_options=True))
def save_from_character(): try: character = g.db.query(Character).filter(and_( Character.id == request.form["character_id"], Character.user_id == g.user.id, )).order_by(Character.title).one() except NoResultFound: abort(404) # Remember old values so we can check if they've changed later. old_name = g.chat_user.name old_acronym = g.chat_user.acronym # Send a message if name, acronym or color has changed. changed = ( g.chat_user.name != character.name or g.chat_user.acronym != character.acronym or g.chat_user.color != character.color ) g.chat_user.name = character.name g.chat_user.acronym = character.acronym g.chat_user.color = character.color g.chat_user.quirk_prefix = character.quirk_prefix g.chat_user.quirk_suffix = character.quirk_suffix g.chat_user.case = character.case g.chat_user.replacements = character.replacements g.chat_user.regexes = character.regexes if changed: if g.chat_user.computed_group == "silent": send_userlist(g.user_list, g.db, g.chat) else: send_message(g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, type="user_info", name=g.chat_user.name, text="%s [%s] is now %s [%s]." % ( old_name, old_acronym, g.chat_user.name, g.chat_user.acronym, ), ), g.user_list) return jsonify(g.chat_user.to_dict(include_options=True))
def unban(chat, pm_user, url, fmt): if chat.type != "group": abort(404) try: own_chat_user = g.db.query(ChatUser).filter(and_( ChatUser.chat_id == chat.id, ChatUser.user_id == g.user.id, )).one() except NoResultFound: abort(404) if not own_chat_user.can("ban"): abort(403) try: unban_chat_user = g.db.query(ChatUser).filter(and_( ChatUser.chat_id == chat.id, ChatUser.number == int(request.form["number"]), )).one() except (NoResultFound, ValueError): abort(404) try: ban = g.db.query(Ban).filter(and_(Ban.chat_id == chat.id, Ban.user_id == unban_chat_user.user_id)).one() except NoResultFound: abort(404) g.db.delete(ban) send_message(g.db, g.redis, Message( chat_id=chat.id, user_id=unban_chat_user.user_id, type="user_action", name=own_chat_user.name, text="%s [%s] unbanned %s [%s] from the chat." % ( own_chat_user.name, own_chat_user.acronym, unban_chat_user.name, unban_chat_user.acronym, ), ), g.user_list) if "Referer" in request.headers: return redirect(request.headers["Referer"]) return redirect(url_for("rp_chat_users", url=url))
def save(): # Remember old values so we can check if they've changed later. old_name = g.chat_user.name old_acronym = g.chat_user.acronym old_color = g.chat_user.color new_details = validate_character_form(request.form) g.chat_user.search_character_id = new_details["search_character_id"] g.chat_user.name = new_details["name"] g.chat_user.acronym = new_details["acronym"] g.chat_user.color = new_details["color"] g.chat_user.quirk_prefix = new_details["quirk_prefix"] g.chat_user.quirk_suffix = new_details["quirk_suffix"] g.chat_user.case = new_details["case"] g.chat_user.replacements = new_details["replacements"] g.chat_user.regexes = new_details["regexes"] # Send a message if name or acronym has changed. if g.chat_user.name != old_name or g.chat_user.acronym != old_acronym: if g.chat_user.computed_group == "silent": send_userlist(g.user_list, g.db, g.chat) else: send_message( g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, type="user_info", name=g.chat_user.name, text=("%s [%s] is now %s [%s].") % ( old_name, old_acronym, g.chat_user.name, g.chat_user.acronym, ), ), g.user_list) # Just refresh the user list if the color has changed. elif g.chat_user.color != old_color: send_userlist(g.user_list, g.db, g.chat) return jsonify(g.chat_user.to_dict(include_options=True))
def set_topic(): # Validation: We must be allowed to set the topic. if not g.chat_user.can("set_topic"): abort(403) topic = request.form["topic"].strip()[:500] # If it hasn't changed, don't bother sending a message about it. if topic == g.chat.topic: return "", 204 g.chat.topic = topic if topic == "": send_message( g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, name=g.chat_user.name, type="chat_meta", text="%s [%s] removed the conversation topic." % ( g.chat_user.name, g.chat_user.acronym, ), ), g.user_list) else: send_message( g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, name=g.chat_user.name, type="chat_meta", text="%s [%s] changed the topic to \"%s\"" % ( g.chat_user.name, g.chat_user.acronym, topic, ), ), g.user_list) return "", 204
def save(): # Remember old values so we can check if they've changed later. old_name = g.chat_user.name old_acronym = g.chat_user.acronym old_color = g.chat_user.color new_details = validate_character_form(request.form) g.chat_user.search_character_id = new_details["search_character_id"] g.chat_user.name = new_details["name"] g.chat_user.acronym = new_details["acronym"] g.chat_user.color = new_details["color"] g.chat_user.quirk_prefix = new_details["quirk_prefix"] g.chat_user.quirk_suffix = new_details["quirk_suffix"] g.chat_user.case = new_details["case"] g.chat_user.replacements = new_details["replacements"] g.chat_user.regexes = new_details["regexes"] # Send a message if name or acronym has changed. if g.chat_user.name != old_name or g.chat_user.acronym != old_acronym: if g.chat_user.computed_group == "silent": send_userlist(g.db, g.redis, g.chat) else: send_message(g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, type="user_info", name=g.chat_user.name, text=("%s [%s] is now %s [%s].") % ( old_name, old_acronym, g.chat_user.name, g.chat_user.acronym, ), )) # Just refresh the user list if the color has changed. elif g.chat_user.color != old_color: send_userlist(g.db, g.redis, g.chat) return jsonify(g.chat_user.to_dict(include_options=True))
def reap_chat(chat_id): user_list = UserListStore(NewparpRedis(connection_pool=redis_chat_pool), chat_id) old_user_ids = user_list.user_ids_online() if not old_user_ids: return with session_scope() as db: chat = db.query(Chat).filter(Chat.id == chat_id).one() chat_users = { _.user_id: _ for _ in db.query(ChatUser).filter( and_( ChatUser.chat_id == chat_id, ChatUser.user_id.in_(old_user_ids), )) } for socket_id, user_id in user_list.inconsistent_entries(): chat_user = chat_users[user_id] dead = user_list.socket_disconnect(socket_id, chat_user.number) if dead: logger.debug("dead: %s" % chat_user) # TODO optimise this when reaping several people at once? if chat_user.computed_group == "silent" or chat.type in ( "pm", "roulette"): send_userlist(user_list, db, chat) else: send_message( db, reap_chat.redis, Message( chat=chat, user_id=chat_user.user_id, type="timeout", name=chat_user.name, text="%s's connection timed out." % chat_user.name, ), user_list)
def uninvite(chat, pm_user, url, fmt): if request.form["username"] == g.user.username: abort(404) if chat.type != "group" or chat.publicity != "private": abort(404) try: own_chat_user = g.db.query(ChatUser).filter(and_( ChatUser.chat_id == chat.id, ChatUser.user_id == g.user.id, )).one() except NoResultFound: abort(404) if not own_chat_user.can("invite"): abort(403) try: invite_user = g.db.query(User).filter( func.lower(User.username) == request.form["username"].lower(), ).one() except NoResultFound: abort(404) if g.db.query(func.count("*")).select_from(Invite).filter(and_( Invite.chat_id == chat.id, Invite.user_id == invite_user.id, )).scalar() != 0: g.db.query(Invite).filter(and_( Invite.chat_id == chat.id, Invite.user_id == invite_user.id, )).delete() # Unsubscribing is impossible if they don't have access to the chat, so # we need to force-unsubscribe them here. try: invite_chat_user = g.db.query(ChatUser).filter(and_( ChatUser.chat_id == chat.id, ChatUser.user_id == invite_user.id, )).one() invite_chat_user.subscribed = False except NoResultFound: pass send_message(g.db, g.redis, Message( chat_id=chat.id, user_id=invite_user.id, type="user_action", name=own_chat_user.name, text="%s [%s] un-invited %s from the chat." % ( own_chat_user.name, own_chat_user.acronym, invite_user.username, ), ), g.user_list) if ( "X-Requested-With" in request.headers and request.headers["X-Requested-With"] == "XMLHttpRequest" ): return "", 204 if "Referer" in request.headers: return redirect(request.headers["Referer"].split("?")[0]) return redirect(url_for("rp_invites", url=url))
def invite(chat, pm_user, url, fmt): if request.form["username"] == g.user.username: abort(404) if chat.type != "group" or chat.publicity != "private": abort(404) try: own_chat_user = g.db.query(ChatUser).filter(and_( ChatUser.chat_id == chat.id, ChatUser.user_id == g.user.id, )).one() except NoResultFound: abort(404) if not own_chat_user.can("invite"): abort(403) try: invite_user = g.db.query(User).filter( func.lower(User.username) == request.form["username"].lower(), ).one() except NoResultFound: if ( "X-Requested-With" in request.headers and request.headers["X-Requested-With"] == "XMLHttpRequest" ): abort(404) return redirect(( request.headers.get("Referer") or url_for("rp_invites", url=url) ).split("?")[0] + "?invite_error=no_user") if g.db.query(func.count("*")).select_from(Invite).filter(and_( Invite.chat_id == chat.id, Invite.user_id == invite_user.id, )).scalar() == 0: g.db.add(Invite(chat_id=chat.id, user_id=invite_user.id, creator_id=g.user.id)) # Subscribe them to the chat and make it unread so they get a notification about it. try: invite_chat_user = g.db.query(ChatUser).filter(and_( ChatUser.chat_id == chat.id, ChatUser.user_id == invite_user.id, )).one() except NoResultFound: new_number = (g.db.query(func.max(ChatUser.number)).filter(ChatUser.chat_id == chat.id).scalar() or 0) + 1 invite_chat_user = ChatUser.from_user(user=invite_user, chat_id=chat.id, number=new_number) # Disable typing notifications by default in group chats. invite_chat_user.typing_notifications = False g.db.add(invite_chat_user) invite_chat_user.subscribed = True invite_chat_user.last_online = chat.last_message - datetime.timedelta(0, 1) send_message(g.db, g.redis, Message( chat_id=chat.id, user_id=invite_user.id, type="user_action", name=own_chat_user.name, text="%s [%s] invited %s to the chat." % ( own_chat_user.name, own_chat_user.acronym, invite_user.username, ), ), g.user_list) if ( "X-Requested-With" in request.headers and request.headers["X-Requested-With"] == "XMLHttpRequest" ): return "", 204 if "Referer" in request.headers: return redirect(request.headers["Referer"].split("?")[0]) return redirect(url_for("rp_invites", url=url))
def run(self, chat_id, data): if len(data["messages"]) == 0: return self.load_lists() for message in data["messages"]: if message["user_number"] is None: continue message["hash"] = hashlib.md5( ":".join([message["color"], message["acronym"], message["text"]]) .encode("utf-8").lower() ).hexdigest() try: self.check_connection_spam(chat_id, message) self.check_banned_names(chat_id, message) self.check_message_filter(chat_id, message) self.check_warnlist(chat_id, message) except Mark as e: with session_scope() as db: q = db.query(Message).filter(Message.id == message["id"]).update({"spam_flag": str(e)}) message.update({"spam_flag": str(e)}) self.redis.publish("spamless:live", json.dumps(message)) except Silence as e: with session_scope() as db: # XXX maybe cache this? try: chat_user, user, chat = db.query( ChatUser, User, AnyChat, ).join( User, ChatUser.user_id == User.id, ).join( AnyChat, ChatUser.chat_id == AnyChat.id, ).filter(and_( ChatUser.chat_id == chat_id, ChatUser.number == message["user_number"], )).one() except NoResultFound: continue if chat.type != "group": flag_suffix = chat.type.upper() elif chat_user.computed_group in ("admin", "creator"): flag_suffix = chat_user.computed_group.upper() else: flag_suffix = "SILENCED" db.query(Message).filter(Message.id == message["id"]).update({ "spam_flag": str(e) + " " + flag_suffix, }) message.update({"spam_flag": str(e) + " " + flag_suffix}) self.redis.publish("spamless:live", json.dumps(message)) if flag_suffix == "SILENCED": db.query(ChatUser).filter(and_( ChatUser.chat_id == chat_id, ChatUser.number == message["user_number"] )).update({"group": "silent"}) send_message(db, self.redis, Message( chat_id=chat_id, type="spamless", name="The Spamless", acronym="\u264b", text="Spam has been detected and silenced. Please come [url=http://help.msparp.com/]here[/url] or ask a chat moderator to unsilence you if this was an accident.", color="626262" ), True)
def set_group(): # Admins and creators can do this from the chat users list, so only require # a socket if we're not one. if ( g.redis.scard("chat:%s:sockets:%s" % (g.chat.id, g.session_id)) == 0 and not (g.user.is_admin or g.user.id == g.chat.creator_id) ): abort(403) # Validation #1: We must be allowed to set groups. if not g.chat_user.can("set_group"): abort(403) # Validation #2: Set group must be lower than ours. # Also 400 if the group is invalid. set_group = request.form["group"] try: if ChatUser.group_ranks[set_group] >= g.chat_user.computed_rank: abort(403) except KeyError: abort(400) # Fetch the ChatUser we're trying to change. try: set_chat_user, set_user = g.db.query(ChatUser, User).join( User, ChatUser.user_id == User.id, ).filter(and_( ChatUser.chat_id == g.chat.id, ChatUser.number == int(request.form["number"]), )).one() except (ValueError, NoResultFound): abort(404) # Validation #3: Set user's group must be lower than ours. if set_chat_user.computed_rank >= g.chat_user.computed_rank: abort(403) # If we're changing them to their current group, just send a 204 without # actually doing anything. if set_group == set_chat_user.group: return "", 204 if set_group == "mod3": message = ("%s [%s] set %s [%s] to Professional Wet Blanket. They can now silence, kick and ban other users.") elif set_group == "mod2": message = ("%s [%s] set %s [%s] to Bum's Rusher. They can now silence and kick other users.") elif set_group == "mod1": message = ("%s [%s] set %s [%s] to Amateur Gavel-Slinger. They can now silence other users.") elif set_group == "user": if set_chat_user.group == "silent": message = ("%s [%s] unsilenced %s [%s].") else: message = ("%s [%s] removed moderator status from %s [%s].") elif set_group == "silent": message = ("%s [%s] silenced %s [%s].") set_chat_user.group = set_group send_message(g.db, g.redis, Message( chat_id=g.chat.id, user_id=set_chat_user.user_id, name=set_chat_user.name, type="user_group", text=message % ( g.chat_user.name, g.chat_user.acronym, set_chat_user.name, set_chat_user.acronym, ), )) return "", 204
def user_action(): action = request.form["action"] if action not in ("kick", "ban"): abort(400) # Validation #1: We must be allowed to perform this action. if not g.chat_user.can(action): abort(403) # Fetch the ChatUser we're trying to act upon. try: set_chat_user, set_user = g.db.query(ChatUser, User).join( User, ChatUser.user_id == User.id, ).filter(and_( ChatUser.chat_id == g.chat.id, ChatUser.number == int(request.form["number"]), )).one() except (ValueError, NoResultFound): abort(404) # Validation #2: Set user's group must be lower than ours. if set_chat_user.computed_rank >= g.chat_user.computed_rank: abort(403) if action == "kick": g.redis.publish( "channel:%s:%s" % (g.chat.id, set_user.id), "{\"exit\":\"kick\"}", ) # Don't allow them back in for 30 seconds. kick_key = "kicked:%s:%s" % (g.chat.id, set_user.id) g.redis.set(kick_key, 1) g.redis.expire(kick_key, 30) # Only send a kick message if they're currently online. if disconnect_user(g.redis, g.chat.id, set_user.id): send_message(g.db, g.redis, Message( chat_id=g.chat.id, user_id=set_user.id, type="user_action", name=g.chat_user.name, text=( "%s [%s] kicked %s [%s] from the chat." ) % ( g.chat_user.name, g.chat_user.acronym, set_chat_user.name, set_chat_user.acronym, ) )) return "", 204 elif action == "ban": # In private chats, this un-invites someone instead of banning them. if g.chat.publicity == "private": deleted = g.db.query(Invite).filter(and_( Invite.chat_id == g.chat.id, Invite.user_id == set_user.id, )).delete() # Don't send a message if there wasn't an invite. if not deleted: return "", 204 ban_message = ( "%s [%s] un-invited %s [%s] from the chat." ) % ( g.chat_user.name, g.chat_user.acronym, set_chat_user.name, set_chat_user.acronym, ) else: # Skip if they're already banned. if g.db.query(func.count('*')).select_from(Ban).filter(and_( Ban.chat_id == g.chat.id, Ban.user_id == set_user.id, )).scalar() != 0: return "", 204 reason = None if "reason" in request.form: reason = request.form["reason"].strip()[:500] or None g.db.add(Ban( user_id=set_user.id, chat_id=g.chat.id, creator_id=g.user.id, reason=reason, )) if request.form.get("reason") is not None: ban_message = ( "%s [%s] banned %s [%s] from the chat. Reason: %s" ) % ( g.chat_user.name, g.chat_user.acronym, set_chat_user.name, set_chat_user.acronym, reason, ) else: ban_message = ( "%s [%s] banned %s [%s] from the chat." ) % ( g.chat_user.name, g.chat_user.acronym, set_chat_user.name, set_chat_user.acronym, ) g.redis.publish( "channel:%s:%s" % (g.chat.id, set_user.id), "{\"exit\":\"ban\"}", ) disconnect_user(g.redis, g.chat.id, set_user.id) send_message(g.db, g.redis, Message( chat_id=g.chat.id, user_id=set_user.id, type="user_action", name=g.chat_user.name, text=ban_message, )) # Unsubscribe if necessary. set_chat_user.subscribed = False return "", 204
def set_flag(): flag = request.form["flag"] value = request.form["value"] if "flag" not in request.form or "value" not in request.form: abort(400) # Validation: We must be allowed to set flags. if not g.chat_user.can("set_flag"): abort(403) # Boolean flags. if (flag in ("autosilence") and value in ("on", "off")): new_value = value == "on" if new_value == getattr(g.chat, flag): return "", 204 setattr(g.chat, flag, new_value) message = ("%%s [%%s] switched %s %s.") % (flag, value) elif (flag == "style" and value in ("script", "paragraph", "either")): if value == g.chat.style: return "", 204 g.chat.style = value if g.chat.style == "script": message = "%s [%s] marked the chat as script style." elif g.chat.style == "paragraph": message = "%s [%s] marked the chat as paragraph style." elif g.chat.style == "either": message = "%s [%s] marked the chat as either style." elif (flag == "level" and value in level_options): if value == g.chat.level: return "", 204 g.chat.level = value if g.chat.level == "sfw": message = "%s [%s] marked the chat as safe for work." elif g.chat.level == "nsfws": message = "%s [%s] marked the chat as not safe for work due to sexual content." elif g.chat.level == "nsfwv": message = "%s [%s] marked the chat as not safe for work due to violent content." elif g.chat.level == "nsfw-extreme": message = "%s [%s] marked the chat as NSFW extreme." # Publicity is also an enum because we might add options for password # protected or invite only chats in the future. elif (flag == "publicity" and value in GroupChat.publicity.type.enums): # Only admins can set/unset pinned and admin_only. admin_values = ("pinned", "admin_only") if (value in admin_values or g.chat.publicity in admin_values) and g.chat_user.computed_group != "admin": abort(403) if value == g.chat.publicity: return "", 204 g.chat.publicity = value if g.chat.publicity == "private": message = "%s [%s] set the chat to private. Only users who have been invited can enter." elif g.chat.publicity == "unlisted": message = "%s [%s] unlisted the chat. Anyone can visit this chat, but only if they have the URL." elif g.chat.publicity == "listed": message = "%s [%s] listed the chat. It's now listed on the public chats page." elif g.chat.publicity == "pinned": message = "%s [%s] pinned the chat. It's now listed at the top of the public chats page." elif g.chat.publicity == "admin_only": message = "%s [%s] restricted the chat to admins." else: abort(400) send_message(g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, type="chat_meta", text=message % (g.chat_user.name, g.chat_user.acronym), )) return "", 204
def set_flag(): flag = request.form["flag"] value = request.form["value"] if "flag" not in request.form or "value" not in request.form: abort(400) # Validation: We must be allowed to set flags. if not g.chat_user.can("set_flag"): abort(403) # Boolean flags. if (flag in ("autosilence") and value in ("on", "off")): new_value = value == "on" if new_value == getattr(g.chat, flag): return "", 204 setattr(g.chat, flag, new_value) message = ("%%s [%%s] switched %s %s.") % (flag, value) elif (flag == "style" and value in ("script", "paragraph", "either")): if value == g.chat.style: return "", 204 g.chat.style = value if g.chat.style == "script": message = "%s [%s] marked the chat as script style." elif g.chat.style == "paragraph": message = "%s [%s] marked the chat as paragraph style." elif g.chat.style == "either": message = "%s [%s] marked the chat as either style." elif (flag == "level" and value in level_options): if value == g.chat.level: return "", 204 g.chat.level = value if g.chat.level == "sfw": message = "%s [%s] marked the chat as safe for work." elif g.chat.level == "nsfws": message = "%s [%s] marked the chat as not safe for work due to sexual content." elif g.chat.level == "nsfwv": message = "%s [%s] marked the chat as not safe for work due to violent content." elif g.chat.level == "nsfw-extreme": message = "%s [%s] marked the chat as NSFW extreme." # Publicity is also an enum because we might add options for password # protected or invite only chats in the future. elif (flag == "publicity" and value in GroupChat.publicity.type.enums): # Only admins can set/unset pinned and admin_only. admin_values = ("pinned", "admin_only") if (value in admin_values or g.chat.publicity in admin_values) and g.chat_user.computed_group != "admin": abort(403) if value == g.chat.publicity: return "", 204 g.chat.publicity = value if g.chat.publicity == "private": message = "%s [%s] set the chat to private. Only users who have been invited can enter." elif g.chat.publicity == "unlisted": message = "%s [%s] unlisted the chat. Anyone can visit this chat, but only if they have the URL." elif g.chat.publicity == "listed": message = "%s [%s] listed the chat. It's now listed on the public chats page." elif g.chat.publicity == "pinned": message = "%s [%s] pinned the chat. It's now listed at the top of the public chats page." elif g.chat.publicity == "admin_only": message = "%s [%s] restricted the chat to admins." else: abort(400) send_message( g.db, g.redis, Message( chat_id=g.chat.id, user_id=g.user.id, type="chat_meta", text=message % (g.chat_user.name, g.chat_user.acronym), ), g.user_list) return "", 204
def user_action(): action = request.form["action"] if action not in ("kick", "ban"): abort(400) # Validation #1: We must be allowed to perform this action. if not g.chat_user.can(action): abort(403) # Fetch the ChatUser we're trying to act upon. try: set_chat_user, set_user = g.db.query(ChatUser, User).join( User, ChatUser.user_id == User.id, ).filter( and_( ChatUser.chat_id == g.chat.id, ChatUser.number == int(request.form["number"]), )).one() except (ValueError, NoResultFound): abort(404) # Validation #2: Set user's group must be lower than ours. if set_chat_user.computed_rank >= g.chat_user.computed_rank: abort(403) if action == "kick": # Don't allow them back in for 30 seconds. kick_key = "kicked:%s:%s" % (g.chat.id, set_user.id) g.redis.set(kick_key, 1) g.redis.expire(kick_key, 30) g.redis.publish( "channel:%s:%s" % (g.chat.id, set_user.id), "{\"exit\":\"kick\"}", ) # Only send a kick message if they're currently online. if g.user_list.user_disconnect(set_user.id, set_chat_user.number): send_message( g.db, g.redis, Message(chat_id=g.chat.id, user_id=set_user.id, type="user_action", name=g.chat_user.name, text=("%s [%s] kicked %s [%s] from the chat.") % ( g.chat_user.name, g.chat_user.acronym, set_chat_user.name, set_chat_user.acronym, )), g.user_list) return "", 204 elif action == "ban": # In private chats, this un-invites someone instead of banning them. if g.chat.publicity == "private": deleted = g.db.query(Invite).filter( and_( Invite.chat_id == g.chat.id, Invite.user_id == set_user.id, )).delete() # Don't send a message if there wasn't an invite. if not deleted: return "", 204 ban_message = ("%s [%s] un-invited %s [%s] from the chat.") % ( g.chat_user.name, g.chat_user.acronym, set_chat_user.name, set_chat_user.acronym, ) else: # Skip if they're already banned. if g.db.query(func.count('*')).select_from(Ban).filter( and_( Ban.chat_id == g.chat.id, Ban.user_id == set_user.id, )).scalar() != 0: return "", 204 reason = None if "reason" in request.form: reason = request.form["reason"].strip()[:500] or None g.db.add( Ban( user_id=set_user.id, chat_id=g.chat.id, creator_id=g.user.id, reason=reason, )) if request.form.get("reason") is not None: ban_message = ( "%s [%s] banned %s [%s] from the chat. Reason: %s") % ( g.chat_user.name, g.chat_user.acronym, set_chat_user.name, set_chat_user.acronym, reason, ) else: ban_message = ("%s [%s] banned %s [%s] from the chat.") % ( g.chat_user.name, g.chat_user.acronym, set_chat_user.name, set_chat_user.acronym, ) g.redis.publish( "channel:%s:%s" % (g.chat.id, set_user.id), "{\"exit\":\"ban\"}", ) g.user_list.user_disconnect(set_user.id, set_chat_user.number) send_message( g.db, g.redis, Message( chat_id=g.chat.id, user_id=set_user.id, type="user_action", name=g.chat_user.name, text=ban_message, ), g.user_list) # Unsubscribe if necessary. set_chat_user.subscribed = False return "", 204
def uninvite(chat, pm_user, url, fmt): if request.form["username"] == g.user.username: abort(404) if chat.type != "group" or chat.publicity != "private": abort(404) try: own_chat_user = g.db.query(ChatUser).filter( and_( ChatUser.chat_id == chat.id, ChatUser.user_id == g.user.id, )).one() except NoResultFound: abort(404) if not own_chat_user.can("invite"): abort(403) try: invite_user = g.db.query(User).filter( func.lower( User.username) == request.form["username"].lower(), ).one() except NoResultFound: abort(404) if g.db.query(func.count("*")).select_from(Invite).filter( and_( Invite.chat_id == chat.id, Invite.user_id == invite_user.id, )).scalar() != 0: g.db.query(Invite).filter( and_( Invite.chat_id == chat.id, Invite.user_id == invite_user.id, )).delete() # Unsubscribing is impossible if they don't have access to the chat, so # we need to force-unsubscribe them here. try: invite_chat_user = g.db.query(ChatUser).filter( and_( ChatUser.chat_id == chat.id, ChatUser.user_id == invite_user.id, )).one() invite_chat_user.subscribed = False except NoResultFound: pass send_message( g.db, g.redis, Message( chat_id=chat.id, user_id=invite_user.id, type="user_action", name=own_chat_user.name, text="%s [%s] un-invited %s from the chat." % ( own_chat_user.name, own_chat_user.acronym, invite_user.username, ), ), g.user_list) if ("X-Requested-With" in request.headers and request.headers["X-Requested-With"] == "XMLHttpRequest"): return "", 204 if "Referer" in request.headers: return redirect(request.headers["Referer"].split("?")[0]) return redirect(url_for("rp_invites", url=url))
def invite(chat, pm_user, url, fmt): if request.form["username"] == g.user.username: abort(404) if chat.type != "group" or chat.publicity != "private": abort(404) try: own_chat_user = g.db.query(ChatUser).filter( and_( ChatUser.chat_id == chat.id, ChatUser.user_id == g.user.id, )).one() except NoResultFound: abort(404) if not own_chat_user.can("invite"): abort(403) try: invite_user = g.db.query(User).filter( func.lower( User.username) == request.form["username"].lower(), ).one() except NoResultFound: if ("X-Requested-With" in request.headers and request.headers["X-Requested-With"] == "XMLHttpRequest"): abort(404) return redirect((request.headers.get("Referer") or url_for("rp_invites", url=url)).split("?")[0] + "?invite_error=no_user") if g.db.query(func.count("*")).select_from(Invite).filter( and_( Invite.chat_id == chat.id, Invite.user_id == invite_user.id, )).scalar() == 0: g.db.add( Invite(chat_id=chat.id, user_id=invite_user.id, creator_id=g.user.id)) # Subscribe them to the chat and make it unread so they get a notification about it. try: invite_chat_user = g.db.query(ChatUser).filter( and_( ChatUser.chat_id == chat.id, ChatUser.user_id == invite_user.id, )).one() except NoResultFound: new_number = (g.db.query(func.max(ChatUser.number)).filter( ChatUser.chat_id == chat.id).scalar() or 0) + 1 invite_chat_user = ChatUser.from_user(user=invite_user, chat_id=chat.id, number=new_number) # Disable typing notifications by default in group chats. invite_chat_user.typing_notifications = False g.db.add(invite_chat_user) invite_chat_user.subscribed = True invite_chat_user.last_online = chat.last_message - timedelta(0, 1) send_message( g.db, g.redis, Message( chat_id=chat.id, user_id=invite_user.id, type="user_action", name=own_chat_user.name, text="%s [%s] invited %s to the chat." % ( own_chat_user.name, own_chat_user.acronym, invite_user.username, ), ), g.user_list) if ("X-Requested-With" in request.headers and request.headers["X-Requested-With"] == "XMLHttpRequest"): return "", 204 if "Referer" in request.headers: return redirect(request.headers["Referer"].split("?")[0]) return redirect(url_for("rp_invites", url=url))
def run(self, chat_id, data): if len(data["messages"]) == 0: return self.load_lists() for message in data["messages"]: if message["user_number"] is None: continue self.redis.set("spamless:last_id", message["id"]) message["hash"] = hashlib.md5( ":".join([message["color"], message["acronym"], message["text"]]) .encode("utf-8").lower() ).hexdigest() try: self.check_connection_spam(chat_id, message) self.check_banned_names(chat_id, message) self.check_message_filter(chat_id, message) self.check_warnlist(chat_id, message) except Mark as e: with session_scope() as db: q = db.query(Message).filter(Message.id == message["id"]).update({"spam_flag": str(e)}) message.update({"spam_flag": str(e)}) self.redis.publish("spamless:live", json.dumps(message)) except Silence as e: with session_scope() as db: # XXX maybe cache this? try: chat_user, user, chat = db.query( ChatUser, User, AnyChat, ).join( User, ChatUser.user_id == User.id, ).join( AnyChat, ChatUser.chat_id == AnyChat.id, ).filter(and_( ChatUser.chat_id == chat_id, ChatUser.number == message["user_number"], )).one() except NoResultFound: continue if chat.type != "group": flag_suffix = chat.type.upper() elif chat_user.computed_group in ("admin", "creator"): flag_suffix = chat_user.computed_group.upper() else: flag_suffix = "SILENCED" db.query(Message).filter(Message.id == message["id"]).update({ "spam_flag": str(e) + " " + flag_suffix, }) message.update({"spam_flag": str(e) + " " + flag_suffix}) self.redis.publish("spamless:live", json.dumps(message)) if flag_suffix == "SILENCED": db.query(ChatUser).filter(and_( ChatUser.chat_id == chat_id, ChatUser.number == message["user_number"] )).update({"group": "silent"}) send_message(db, self.redis, Message( chat_id=chat_id, type="spamless", name="The Spamless", acronym="\u264b", text="Spam has been detected and silenced. Please come [url=http://help.msparp.com/]here[/url] or ask a chat moderator to unsilence you if this was an accident.", color="626262" ), force_userlist=True)
def set_group(): # Admins and creators can do this from the chat users list, so only require # a socket if we're not one. if (not g.user_list.session_has_open_socket(g.session_id, g.user.id) and not (g.user.is_admin or g.user.id == g.chat.creator_id)): abort(403) # Validation #1: We must be allowed to set groups. if not g.chat_user.can("set_group"): abort(403) # Validation #2: Set group must be lower than ours. # Also 400 if the group is invalid. set_group = request.form["group"] try: if ChatUser.group_ranks[set_group] >= g.chat_user.computed_rank: abort(403) except KeyError: abort(400) # Fetch the ChatUser we're trying to change. try: set_chat_user, set_user = g.db.query(ChatUser, User).join( User, ChatUser.user_id == User.id, ).filter( and_( ChatUser.chat_id == g.chat.id, ChatUser.number == int(request.form["number"]), )).one() except (ValueError, NoResultFound): abort(404) # Validation #3: Set user's group must be lower than ours. if set_chat_user.computed_rank >= g.chat_user.computed_rank: abort(403) # If we're changing them to their current group, just send a 204 without # actually doing anything. if set_group == set_chat_user.group: return "", 204 if set_group == "mod3": message = ( "%s [%s] set %s [%s] to Professional Wet Blanket. They can now silence, kick and ban other users." ) elif set_group == "mod2": message = ( "%s [%s] set %s [%s] to Bum's Rusher. They can now silence and kick other users." ) elif set_group == "mod1": message = ( "%s [%s] set %s [%s] to Amateur Gavel-Slinger. They can now silence other users." ) elif set_group == "user": if set_chat_user.group == "silent": message = ("%s [%s] unsilenced %s [%s].") else: message = ("%s [%s] removed moderator status from %s [%s].") elif set_group == "silent": message = ("%s [%s] silenced %s [%s].") set_chat_user.group = set_group send_message( g.db, g.redis, Message( chat_id=g.chat.id, user_id=set_chat_user.user_id, name=set_chat_user.name, type="user_group", text=message % ( g.chat_user.name, g.chat_user.acronym, set_chat_user.name, set_chat_user.acronym, ), ), g.user_list) return "", 204