Aevann 2024-08-09 16:35:36 +03:00
parent e4ee14b955
commit ec51c1380c
10 changed files with 79 additions and 32 deletions

View File

@ -7116,7 +7116,7 @@ input[type=number] {
} }
} }
.chat-owner { .chat-mod {
height: 30px; height: 30px;
} }

View File

@ -16,13 +16,15 @@ class Chat(Base):
created_utc = Column(Integer) created_utc = Column(Integer)
css = deferred(Column(String)) 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 @property
@lazy @lazy
def owner_id(self): 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() return self.mod_ids[0] or AUTOJANNY_ID
if not owner_id:
return AUTOJANNY_ID
return owner_id[0]
@property @property
@lazy @lazy
@ -45,6 +47,7 @@ class ChatMembership(Base):
muted = Column(Boolean, default=False) muted = Column(Boolean, default=False)
mentions = Column(Integer, default=0) mentions = Column(Integer, default=0)
created_utc = Column(Integer) created_utc = Column(Integer)
is_mod = Column(Boolean, default=False)
user = relationship("User") user = relationship("User")

View File

@ -136,7 +136,7 @@ def add_notif(cid, uid, text, pushnotif_url='', check_existing=True):
push_notif({uid}, 'New notification', text, pushnotif_url) 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 # Restrict young accounts from generating notifications
if v.age < NOTIFICATION_SPAM_AGE_THRESHOLD: if v.age < NOTIFICATION_SPAM_AGE_THRESHOLD:
return set() 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 i.group(1) == 'everyone':
if chat: if chat:
if not v.id == chat.owner_id: if not membership.is_mod:
abort(403, "You need to be the chat owner to do that!") abort(403, "You need to be the chat owner or a mod to do that!")
group = None 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} member_ids = set(x[0] for x in g.db.query(ChatMembership.user_id).filter_by(chat_id=chat.id).all()) - {v.id}
else: else:

View File

@ -15,6 +15,8 @@ group_mention_regex = re.compile('(?<![:/\w])!([\w-]{3,25})' + NOT_IN_CODE_OR_LI
chat_adding_regex = re.compile('\+@([\w-]{1,30})' + NOT_IN_CODE_OR_LINKS, flags=re.A) chat_adding_regex = re.compile('\+@([\w-]{1,30})' + NOT_IN_CODE_OR_LINKS, flags=re.A)
chat_kicking_regex = re.compile('\-@([\w-]{1,30})' + NOT_IN_CODE_OR_LINKS, flags=re.A) chat_kicking_regex = re.compile('\-@([\w-]{1,30})' + NOT_IN_CODE_OR_LINKS, flags=re.A)
chat_jannying_regex = re.compile('\*@([\w-]{1,30})' + NOT_IN_CODE_OR_LINKS, flags=re.A)
chat_dejannying_regex = re.compile(r'(^|\s)/@([\w-]{1,30})' + NOT_IN_CODE_OR_LINKS, flags=re.A)
everyone_regex = re.compile('(^|\s|>)!(everyone)' + NOT_IN_CODE_OR_LINKS, flags=re.A) everyone_regex = re.compile('(^|\s|>)!(everyone)' + NOT_IN_CODE_OR_LINKS, flags=re.A)

View File

@ -69,9 +69,10 @@ def speak(data, v):
if chat.id == 1: if chat.id == 1:
if not v.allowed_in_chat: return '' if not v.allowed_in_chat: return ''
membership = None
else: else:
is_member = g.db.query(ChatMembership.user_id).filter_by(user_id=v.id, chat_id=chat_id).one_or_none() membership = g.db.query(ChatMembership.is_mod).filter_by(user_id=v.id, chat_id=chat_id).one_or_none()
if not is_member: return '' if not membership: return ''
image = None image = None
if data['file']: if data['file']:
@ -149,7 +150,7 @@ def speak(data, v):
ChatMembership.user_id != v.id, ChatMembership.user_id != v.id,
).one()) ).one())
else: 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': if notify_users == 'everyone':
notify_users = set() notify_users = set()
if chat_message.quotes: if chat_message.quotes:
@ -164,7 +165,7 @@ def speak(data, v):
typing[request.referrer] = [] typing[request.referrer] = []
if v.id == chat.owner_id: if membership.is_mod:
for i in chat_adding_regex.finditer(text): for i in chat_adding_regex.finditer(text):
user = get_user(i.group(1), graceful=True, attributes=[User.id]) 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): 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() g.db.flush()
send_notification(user.id, f"@{v.username} kicked you from their chat [{chat.name}](/chat/{chat.id})") 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: if chat.id != 1:
alrdy_here = set(online[request.referrer].keys()) alrdy_here = set(online[request.referrer].keys())
memberships = g.db.query(ChatMembership).options(load_only(ChatMembership.user_id)).filter( memberships = g.db.query(ChatMembership).options(load_only(ChatMembership.user_id)).filter(

View File

@ -48,6 +48,7 @@ def chat_user(v, username):
chat_membership = ChatMembership( chat_membership = ChatMembership(
user_id=v.id, user_id=v.id,
chat_id=chat.id, chat_id=chat.id,
is_mod=True,
) )
g.db.add(chat_membership) g.db.add(chat_membership)
@ -128,8 +129,8 @@ def change_chat_name(v, chat_id):
if not chat: if not chat:
abort(404, "Chat not found!") abort(404, "Chat not found!")
if v.id != chat.owner_id: if v.id not in chat.mod_ids:
abort(403, "Only the chat owner can change its name!") abort(403, "You need to be the chat owner or a mod to do that!")
new_name = request.values.get("new_name").strip() new_name = request.values.get("new_name").strip()
@ -158,6 +159,12 @@ def leave_chat(v, chat_id):
g.db.delete(membership) 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!"} return {"message": "Chat left successfully!"}
@app.post("/chat/<int:chat_id>/toggle_mute") @app.post("/chat/<int:chat_id>/toggle_mute")
@ -197,8 +204,8 @@ def chat_custom_css_get(v, chat_id):
if not chat: if not chat:
abort(404, "Chat not found!") abort(404, "Chat not found!")
if v.id != chat.owner_id: if v.id not in chat.mod_ids:
abort(403, "Only the chat owner can change its custom css!") 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) 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: if not chat:
abort(404, "Chat not found!") abort(404, "Chat not found!")
if v.id != chat.owner_id: if v.id not in chat.mod_ids:
abort(403, "Only the chat owner can change its custom css!") abort(403, "You need to be the chat owner or a mod to do that!")
if v.shadowbanned: abort(400) if v.shadowbanned: abort(400)
@ -257,8 +264,8 @@ def orgy_control(v, chat_id):
if chat.id == 1: if chat.id == 1:
if v.admin_level < PERMS["ORGIES"]: if v.admin_level < PERMS["ORGIES"]:
abort(403, "Your admin-level is not sufficient enough for this action!") abort(403, "Your admin-level is not sufficient enough for this action!")
elif v.id != chat.owner_id: elif v.id not in chat.mod_ids:
abort(403, "Only the chat owner can manage its orgies!") 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() 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) 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 chat.id == 1:
if v.admin_level < PERMS["ORGIES"]: if v.admin_level < PERMS["ORGIES"]:
abort(403, "Your admin-level is not sufficient enough for this action!") abort(403, "Your admin-level is not sufficient enough for this action!")
elif v.id != chat.owner_id: elif v.id not in chat.mod_ids:
abort(403, "Only the chat owner can manage its orgies!") abort(403, "You need to be the chat owner or a mod to do that!")
link = request.values.get("link", "").strip() link = request.values.get("link", "").strip()
title = request.values.get("title", "").strip() title = request.values.get("title", "").strip()
@ -374,8 +381,8 @@ def remove_orgy(v, created_utc, chat_id):
if chat.id == 1: if chat.id == 1:
if v.admin_level < PERMS["ORGIES"]: if v.admin_level < PERMS["ORGIES"]:
abort(403, "Your admin-level is not sufficient enough for this action!") abort(403, "Your admin-level is not sufficient enough for this action!")
elif v.id != chat.owner_id: elif v.id not in chat.mod_ids:
abort(403, "Only the chat owner can manage its orgies!") 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() orgy = g.db.query(Orgy).filter_by(created_utc=created_utc).one_or_none()

View File

@ -16,7 +16,7 @@
<h5 class="mt-2 mb-3 ml-1 toggleable d-mob-none" style="display:inline-block">{{chat.name}}</h5> <h5 class="mt-2 mb-3 ml-1 toggleable d-mob-none" style="display:inline-block">{{chat.name}}</h5>
<b class="mt-2 mb-2 ml-1 text-small toggleable d-md-none" style="display:inline-block">{{chat.name}}</b> <b class="mt-2 mb-2 ml-1 text-small toggleable d-md-none" style="display:inline-block">{{chat.name}}</b>
{% if v.id == chat.owner_id %} {% if v.id in chat.mod_ids %}
<button class="chat-control fas fa-pen text-small text-muted px-2 toggleable" type="button" data-nonce="{{g.nonce}}" data-onclick="toggleElement('chat-name-form', 'chat-name')"> <button class="chat-control fas fa-pen text-small text-muted px-2 toggleable" type="button" data-nonce="{{g.nonce}}" data-onclick="toggleElement('chat-name-form', 'chat-name')">
</button> </button>
@ -84,11 +84,13 @@
</a> </a>
{% if user.id == chat.owner_id %} {% if user.id == chat.owner_id %}
<img class="ml-1 chat-owner" data-bs-toggle="tooltip" alt="Owner" title="Owner" src="{{SITE_FULL_IMAGES}}/e/marseykingretard.webp"> <img class="ml-1 chat-mod" data-bs-toggle="tooltip" alt="Owner" title="Owner" src="{{SITE_FULL_IMAGES}}/e/marseykingretard.webp">
{% elif user.id in chat.mod_ids %}
<img class="ml-1 chat-mod" data-bs-toggle="tooltip" alt="Mod" title="Mod" src="{{SITE_FULL_IMAGES}}/e/marseyjanny.webp">
{% endif %} {% endif %}
{% if membership.muted %} {% if membership.muted %}
<i class="fas fa-bell-slash text-danger" data-bs-toggle="tooltip" title="Muted chat ({% if v.id == membership.user_id %}kys{% elif v.id == chat.owner_id %}kick him{% else %}kill him{% endif %})"></i> <i class="fas fa-bell-slash text-danger" data-bs-toggle="tooltip" title="Muted chat ({% if v.id == membership.user_id %}kys{% elif v.id in chat.mod_ids %}kick him{% else %}kill him{% endif %})"></i>
{% endif %} {% endif %}
<i class="d-none ml-1 text-smaller text-success online-marker online-marker-{{user.id}} fas fa-circle"></i> <i class="d-none ml-1 text-smaller text-success online-marker online-marker-{{user.id}} fas fa-circle"></i>

View File

@ -538,11 +538,13 @@
</a> </a>
{% if user.id == chat.owner_id %} {% if user.id == chat.owner_id %}
<img class="ml-1 chat-owner" data-bs-toggle="tooltip" alt="Owner" title="Owner" src="{{SITE_FULL_IMAGES}}/e/marseykingretard.webp"> <img class="ml-1 chat-mod" data-bs-toggle="tooltip" alt="Owner" title="Owner" src="{{SITE_FULL_IMAGES}}/e/marseykingretard.webp">
{% elif user.id in chat.mod_ids %}
<img class="ml-1 chat-mod" data-bs-toggle="tooltip" alt="Mod" title="Mod" src="{{SITE_FULL_IMAGES}}/e/marseyjanny.webp">
{% endif %} {% endif %}
{% if membership.muted %} {% if membership.muted %}
<i class="fas fa-bell-slash text-danger" data-bs-toggle="tooltip" title="Muted chat ({% if v.id == membership.user_id %}kys{% elif v.id == chat.owner_id %}kick him{% else %}kill him{% endif %})"></i> <i class="fas fa-bell-slash text-danger" data-bs-toggle="tooltip" title="Muted chat ({% if v.id == membership.user_id %}kys{% elif v.id in chat.mod_ids %}kick him{% else %}kill him{% endif %})"></i>
{% endif %} {% endif %}
<i class="d-none ml-1 text-smaller text-success online-marker online-marker-{{user.id}} fas fa-circle"></i> <i class="d-none ml-1 text-smaller text-success online-marker online-marker-{{user.id}} fas fa-circle"></i>

View File

@ -14,7 +14,7 @@
<h5 class="mt-2 mb-3 ml-1 d-mob-none" style="display:inline-block">{{orgy.title}}</h5> <h5 class="mt-2 mb-3 ml-1 d-mob-none" style="display:inline-block">{{orgy.title}}</h5>
<b class="mt-2 mb-2 ml-1 text-small d-md-none" style="display:inline-block">{{orgy.title}}</b> <b class="mt-2 mb-2 ml-1 text-small d-md-none" style="display:inline-block">{{orgy.title}}</b>
{% if v.id == chat.owner_id %} {% if v.id in chat.mod_ids %}
<a href="/chat/{{chat.id}}/orgies" class="chat-control fas fa-tv text-muted mx-2"></a> <a href="/chat/{{chat.id}}/orgies" class="chat-control fas fa-tv text-muted mx-2"></a>
<a href="/chat/{{chat.id}}/custom_css" class="chat-control fas fa-palette text-muted ml-2"></a> <a href="/chat/{{chat.id}}/custom_css" class="chat-control fas fa-palette text-muted ml-2"></a>
@ -95,11 +95,13 @@
</a> </a>
{% if user.id == chat.owner_id %} {% if user.id == chat.owner_id %}
<img class="ml-1 chat-owner" data-bs-toggle="tooltip" alt="Owner" title="Owner" src="{{SITE_FULL_IMAGES}}/e/marseykingretard.webp"> <img class="ml-1 chat-mod" data-bs-toggle="tooltip" alt="Owner" title="Owner" src="{{SITE_FULL_IMAGES}}/e/marseykingretard.webp">
{% elif user.id in chat.mod_ids %}
<img class="ml-1 chat-mod" data-bs-toggle="tooltip" alt="Mod" title="Mod" src="{{SITE_FULL_IMAGES}}/e/marseyjanny.webp">
{% endif %} {% endif %}
{% if membership.muted %} {% if membership.muted %}
<i class="fas fa-bell-slash text-danger" data-bs-toggle="tooltip" title="Muted chat ({% if v.id == membership.user_id %}kys{% elif v.id == chat.owner_id %}kick him{% else %}kill him{% endif %})"></i> <i class="fas fa-bell-slash text-danger" data-bs-toggle="tooltip" title="Muted chat ({% if v.id == membership.user_id %}kys{% elif v.id in chat.mod_ids %}kick him{% else %}kill him{% endif %})"></i>
{% endif %} {% endif %}
<i class="d-none ml-1 text-smaller text-success online-marker online-marker-{{user.id}} fas fa-circle"></i> <i class="d-none ml-1 text-smaller text-success online-marker online-marker-{{user.id}} fas fa-circle"></i>

View File

@ -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$;