diff --git a/files/assets/css/main.css b/files/assets/css/main.css index cf0665045..6b02e495a 100644 --- a/files/assets/css/main.css +++ b/files/assets/css/main.css @@ -7116,7 +7116,7 @@ input[type=number] { } } -.chat-owner { +.chat-mod { height: 30px; } diff --git a/files/classes/chats.py b/files/classes/chats.py index fb44bc507..f27139451 100644 --- a/files/classes/chats.py +++ b/files/classes/chats.py @@ -16,13 +16,15 @@ class Chat(Base): created_utc = Column(Integer) css = deferred(Column(String)) + @property + @lazy + def mod_ids(self): + return [x[0] for x in g.db.query(ChatMembership.user_id).filter_by(chat_id=self.id, is_mod=True).order_by(ChatMembership.created_utc, ChatMembership.user_id).all()] + @property @lazy def owner_id(self): - owner_id = g.db.query(ChatMembership.user_id).filter_by(chat_id=self.id).order_by(ChatMembership.created_utc, ChatMembership.user_id).first() - if not owner_id: - return AUTOJANNY_ID - return owner_id[0] + return self.mod_ids[0] or AUTOJANNY_ID @property @lazy @@ -45,6 +47,7 @@ class ChatMembership(Base): muted = Column(Boolean, default=False) mentions = Column(Integer, default=0) created_utc = Column(Integer) + is_mod = Column(Boolean, default=False) user = relationship("User") diff --git a/files/helpers/alerts.py b/files/helpers/alerts.py index 0966bb376..3459b2b34 100644 --- a/files/helpers/alerts.py +++ b/files/helpers/alerts.py @@ -136,7 +136,7 @@ def add_notif(cid, uid, text, pushnotif_url='', check_existing=True): push_notif({uid}, 'New notification', text, pushnotif_url) -def NOTIFY_USERS(text, v, oldtext=None, ghost=False, obj=None, followers_ping=True, commenters_ping_post_id=None, charge=True, chat=None): +def NOTIFY_USERS(text, v, oldtext=None, ghost=False, obj=None, followers_ping=True, commenters_ping_post_id=None, charge=True, chat=None, membership=None): # Restrict young accounts from generating notifications if v.age < NOTIFICATION_SPAM_AGE_THRESHOLD: return set() @@ -194,8 +194,8 @@ def NOTIFY_USERS(text, v, oldtext=None, ghost=False, obj=None, followers_ping=Tr if i.group(1) == 'everyone': if chat: - if not v.id == chat.owner_id: - abort(403, "You need to be the chat owner to do that!") + if not membership.is_mod: + abort(403, "You need to be the chat owner or a mod to do that!") group = None member_ids = set(x[0] for x in g.db.query(ChatMembership.user_id).filter_by(chat_id=chat.id).all()) - {v.id} else: diff --git a/files/helpers/regex.py b/files/helpers/regex.py index 6a61d4ec4..ab6a30b53 100644 --- a/files/helpers/regex.py +++ b/files/helpers/regex.py @@ -15,6 +15,8 @@ group_mention_regex = re.compile('(?)!(everyone)' + NOT_IN_CODE_OR_LINKS, flags=re.A) diff --git a/files/routes/chat.py b/files/routes/chat.py index 633e91f4d..3af6b56a0 100644 --- a/files/routes/chat.py +++ b/files/routes/chat.py @@ -69,9 +69,10 @@ def speak(data, v): if chat.id == 1: if not v.allowed_in_chat: return '' + membership = None else: - is_member = g.db.query(ChatMembership.user_id).filter_by(user_id=v.id, chat_id=chat_id).one_or_none() - if not is_member: return '' + membership = g.db.query(ChatMembership.is_mod).filter_by(user_id=v.id, chat_id=chat_id).one_or_none() + if not membership: return '' image = None if data['file']: @@ -149,7 +150,7 @@ def speak(data, v): ChatMembership.user_id != v.id, ).one()) else: - notify_users = NOTIFY_USERS(chat_message.text, v, chat=chat) + notify_users = NOTIFY_USERS(chat_message.text, v, chat=chat, membership=membership) if notify_users == 'everyone': notify_users = set() if chat_message.quotes: @@ -164,7 +165,7 @@ def speak(data, v): typing[request.referrer] = [] - if v.id == chat.owner_id: + if membership.is_mod: for i in chat_adding_regex.finditer(text): user = get_user(i.group(1), graceful=True, attributes=[User.id]) if user and not user.has_muted(v) and not user.has_blocked(v): @@ -186,6 +187,23 @@ def speak(data, v): g.db.flush() send_notification(user.id, f"@{v.username} kicked you from their chat [{chat.name}](/chat/{chat.id})") + if v.id == chat.owner_id: + for i in chat_jannying_regex.finditer(text): + user = get_user(i.group(1), graceful=True, attributes=[User.id]) + if user: + existing = g.db.query(ChatMembership).filter_by(user_id=user.id, chat_id=chat_id, is_mod=False).one_or_none() + if existing: + existing.is_mod = True + send_notification(user.id, f"@{v.username} has added you as a mod of their chat [{chat.name}](/chat/{chat.id})") + + for i in chat_dejannying_regex.finditer(text): + user = get_user(i.group(2), graceful=True, attributes=[User.id]) + if user: + existing = g.db.query(ChatMembership).filter_by(user_id=user.id, chat_id=chat_id, is_mod=True).one_or_none() + if existing: + existing.is_mod = False + send_notification(user.id, f"@{v.username} has removed you as a mod of their chat [{chat.name}](/chat/{chat.id})") + if chat.id != 1: alrdy_here = set(online[request.referrer].keys()) memberships = g.db.query(ChatMembership).options(load_only(ChatMembership.user_id)).filter( diff --git a/files/routes/chats.py b/files/routes/chats.py index dae1a923b..501c8eda5 100644 --- a/files/routes/chats.py +++ b/files/routes/chats.py @@ -48,6 +48,7 @@ def chat_user(v, username): chat_membership = ChatMembership( user_id=v.id, chat_id=chat.id, + is_mod=True, ) g.db.add(chat_membership) @@ -128,8 +129,8 @@ def change_chat_name(v, chat_id): if not chat: abort(404, "Chat not found!") - if v.id != chat.owner_id: - abort(403, "Only the chat owner can change its name!") + if v.id not in chat.mod_ids: + abort(403, "You need to be the chat owner or a mod to do that!") new_name = request.values.get("new_name").strip() @@ -158,6 +159,12 @@ def leave_chat(v, chat_id): g.db.delete(membership) + g.db.flush() + if not chat.mod_ids: + oldest_membership = g.db.query(ChatMembership).filter_by(chat_id=chat.id).order_by(ChatMembership.created_utc, ChatMembership.user_id).first() + oldest_membership.is_mod = True + g.db.add(oldest_membership) + return {"message": "Chat left successfully!"} @app.post("/chat//toggle_mute") @@ -197,8 +204,8 @@ def chat_custom_css_get(v, chat_id): if not chat: abort(404, "Chat not found!") - if v.id != chat.owner_id: - abort(403, "Only the chat owner can change its custom css!") + if v.id not in chat.mod_ids: + abort(403, "You need to be the chat owner or a mod to do that!") return render_template("chat_css.html", v=v, chat=chat) @@ -213,8 +220,8 @@ def chat_custom_css_post(v, chat_id): if not chat: abort(404, "Chat not found!") - if v.id != chat.owner_id: - abort(403, "Only the chat owner can change its custom css!") + if v.id not in chat.mod_ids: + abort(403, "You need to be the chat owner or a mod to do that!") if v.shadowbanned: abort(400) @@ -257,8 +264,8 @@ def orgy_control(v, chat_id): if chat.id == 1: if v.admin_level < PERMS["ORGIES"]: abort(403, "Your admin-level is not sufficient enough for this action!") - elif v.id != chat.owner_id: - abort(403, "Only the chat owner can manage its orgies!") + elif v.id not in chat.mod_ids: + abort(403, "You need to be the chat owner or a mod to do that!") orgies = g.db.query(Orgy).filter_by(chat_id=chat_id).order_by(Orgy.start_utc).all() return render_template("orgy_control.html", v=v, orgies=orgies, chat=chat) @@ -277,8 +284,8 @@ def schedule_orgy(v, chat_id): if chat.id == 1: if v.admin_level < PERMS["ORGIES"]: abort(403, "Your admin-level is not sufficient enough for this action!") - elif v.id != chat.owner_id: - abort(403, "Only the chat owner can manage its orgies!") + elif v.id not in chat.mod_ids: + abort(403, "You need to be the chat owner or a mod to do that!") link = request.values.get("link", "").strip() title = request.values.get("title", "").strip() @@ -374,8 +381,8 @@ def remove_orgy(v, created_utc, chat_id): if chat.id == 1: if v.admin_level < PERMS["ORGIES"]: abort(403, "Your admin-level is not sufficient enough for this action!") - elif v.id != chat.owner_id: - abort(403, "Only the chat owner can manage its orgies!") + elif v.id not in chat.mod_ids: + abort(403, "You need to be the chat owner or a mod to do that!") orgy = g.db.query(Orgy).filter_by(created_utc=created_utc).one_or_none() diff --git a/files/templates/chat.html b/files/templates/chat.html index da8ca5aaa..064e3cec2 100644 --- a/files/templates/chat.html +++ b/files/templates/chat.html @@ -16,7 +16,7 @@
{{chat.name}}
{{chat.name}} - {% if v.id == chat.owner_id %} + {% if v.id in chat.mod_ids %} @@ -84,11 +84,13 @@ {% if user.id == chat.owner_id %} - Owner + Owner + {% elif user.id in chat.mod_ids %} + Mod {% endif %} {% if membership.muted %} - + {% endif %} diff --git a/files/templates/header.html b/files/templates/header.html index 775af76b1..61fdc9d27 100644 --- a/files/templates/header.html +++ b/files/templates/header.html @@ -538,11 +538,13 @@ {% if user.id == chat.owner_id %} - Owner + Owner + {% elif user.id in chat.mod_ids %} + Mod {% endif %} {% if membership.muted %} - + {% endif %} diff --git a/files/templates/orgy.html b/files/templates/orgy.html index 9c655e420..f19751ae2 100644 --- a/files/templates/orgy.html +++ b/files/templates/orgy.html @@ -14,7 +14,7 @@
{{orgy.title}}
{{orgy.title}} - {% if v.id == chat.owner_id %} + {% if v.id in chat.mod_ids %} @@ -95,11 +95,13 @@ {% if user.id == chat.owner_id %} - Owner + Owner + {% elif user.id in chat.mod_ids %} + Mod {% endif %} {% if membership.muted %} - + {% endif %} diff --git a/migrations/20240809-add-chat-jannies.sql b/migrations/20240809-add-chat-jannies.sql new file mode 100644 index 000000000..1f3d87021 --- /dev/null +++ b/migrations/20240809-add-chat-jannies.sql @@ -0,0 +1,11 @@ +alter table chat_memberships add column is_mod bool not null default false; +alter table chat_memberships alter column is_mod drop default; + +DO +$do$ +BEGIN + FOR i IN 1..600 LOOP + update chat_memberships m1 set is_mod=true where chat_id = i and user_id = (select user_id from chat_memberships m2 where m1.chat_id = m2.chat_id order by created_utc limit 1); + END LOOP; +END +$do$;