forked from rDrama/rDrama
alt graph
parent
23faabd467
commit
0e6b144ed7
|
@ -1,11 +1,11 @@
|
|||
import random
|
||||
from operator import *
|
||||
from typing import Any, Union
|
||||
from typing import Callable, Union
|
||||
|
||||
import pyotp
|
||||
from sqlalchemy import Column, ForeignKey
|
||||
from sqlalchemy.orm import aliased, deferred
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.orm import aliased, deferred, Query
|
||||
from sqlalchemy.sql import case, func, literal
|
||||
from sqlalchemy.sql.expression import not_, and_, or_
|
||||
from sqlalchemy.sql.sqltypes import *
|
||||
|
||||
|
@ -470,19 +470,43 @@ class User(Base):
|
|||
def age(self):
|
||||
return int(time.time()) - self.created_utc
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def alts_unique(self):
|
||||
alts = []
|
||||
for u in self.alts:
|
||||
if u not in alts: alts.append(u)
|
||||
return alts
|
||||
def get_alt_graph(self, db:scoped_session, alt_filter:Optional[Callable[[Query], Query]]=None, **kwargs) -> Query:
|
||||
'''
|
||||
Gets the full graph of alts (optionally filtering `Alt` objects by criteria using a callable,
|
||||
such as by a date to only get alts from a certain date) as a query of users that can be filtered
|
||||
further. This function filters alts marked as deleted by default, pass `include_deleted=True` to
|
||||
disable this behavior and include delinked alts.
|
||||
'''
|
||||
if not alt_filter:
|
||||
alt_filter = lambda q:q
|
||||
|
||||
if not kwargs.get('include_deleted', False):
|
||||
deleted_filter = lambda q:q.filter(Alt.deleted == False)
|
||||
else:
|
||||
deleted_filter = lambda q:q
|
||||
|
||||
combined_filter = lambda q:deleted_filter(alt_filter(q))
|
||||
|
||||
alt_graph_cte = db.query(literal(self.id).label('user_id')).select_from(Alt).cte('alt_graph', recursive=True)
|
||||
|
||||
alt_graph_cte_inner = combined_filter(db.query(
|
||||
case(
|
||||
(Alt.user1 == alt_graph_cte.c.user_id, Alt.user2),
|
||||
(Alt.user2 == alt_graph_cte.c.user_id, Alt.user1),
|
||||
)
|
||||
).select_from(Alt, alt_graph_cte).filter(
|
||||
or_(alt_graph_cte.c.user_id == Alt.user1, alt_graph_cte.c.user_id == Alt.user2)
|
||||
))
|
||||
|
||||
alt_graph_cte = alt_graph_cte.union(alt_graph_cte_inner)
|
||||
return db.query(User).filter(User.id == alt_graph_cte.c.user_id)
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def alts_patron(self):
|
||||
for u in self.alts_unique:
|
||||
if u.patron: return True
|
||||
for u in self.get_alt_graph(g.db):
|
||||
if not u._deleted and u.patron: return True
|
||||
return False
|
||||
|
||||
@property
|
||||
|
@ -724,43 +748,10 @@ class User(Base):
|
|||
def do_reddit(self):
|
||||
return self.notifications_count == self.reddit_notifications_count
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def alts(self):
|
||||
subq = g.db.query(Alt).filter(
|
||||
or_(
|
||||
Alt.user1 == self.id,
|
||||
Alt.user2 == self.id
|
||||
)
|
||||
).subquery()
|
||||
|
||||
data = g.db.query(
|
||||
User,
|
||||
aliased(Alt, alias=subq)
|
||||
).join(
|
||||
subq,
|
||||
or_(
|
||||
subq.c.user1 == User.id,
|
||||
subq.c.user2 == User.id
|
||||
)
|
||||
).filter(
|
||||
User.id != self.id
|
||||
).order_by(User.username).all()
|
||||
|
||||
output = []
|
||||
for x in data:
|
||||
user = x[0]
|
||||
user._is_manual = x[1].is_manual
|
||||
user._alt_deleted = x[1].deleted
|
||||
user._alt_created_utc = x[1].created_utc
|
||||
output.append(user)
|
||||
|
||||
return output
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def alt_ids(self):
|
||||
return [x.id for x in self.alts if not x._alt_deleted]
|
||||
return [x.id for x in self.get_alt_graph(g.db) if not x._alt_deleted]
|
||||
|
||||
@property
|
||||
@lazy
|
||||
|
|
|
@ -458,7 +458,7 @@ def execute_antispam_comment_check(body:str, v:User):
|
|||
g.db.commit()
|
||||
abort(403, "Too much spam!")
|
||||
|
||||
def execute_under_siege(v:User, target:Optional[Union[Submission, Comment]], body, type:str):
|
||||
def execute_under_siege(v:User, target:Optional[Union[Submission, Comment]], body, type:str) -> bool:
|
||||
if not get_setting("under_siege"): return True
|
||||
if v.age < UNDER_SIEGE_AGE_THRESHOLD and not v.admin_level >= PERMS['SITE_BYPASS_UNDER_SIEGE_MODE']:
|
||||
v.shadowbanned = AUTOJANNY_ID
|
||||
|
|
|
@ -95,67 +95,15 @@ def merge(v:User, id1, id2):
|
|||
return redirect(user1.url)
|
||||
|
||||
|
||||
@app.get('/admin/merge_all/<id>')
|
||||
@admin_level_required(PERMS['USER_MERGE'])
|
||||
def merge_all(v:User, id):
|
||||
if v.id != AEVANN_ID: abort(403)
|
||||
|
||||
if time.time() - session.get('verified', 0) > 3:
|
||||
session.pop("lo_user", None)
|
||||
path = request.path
|
||||
qs = urlencode(dict(request.values))
|
||||
argval = quote(f"{path}?{qs}", safe='')
|
||||
return redirect(f"/login?redirect={argval}")
|
||||
|
||||
user = get_account(id)
|
||||
|
||||
alt_ids = [x.id for x in user.alts_unique]
|
||||
|
||||
things = g.db.query(AwardRelationship).filter(AwardRelationship.user_id.in_(alt_ids)).all() + g.db.query(Mod).filter(Mod.user_id.in_(alt_ids)).all() + g.db.query(Exile).filter(Exile.user_id.in_(alt_ids)).all()
|
||||
for thing in things:
|
||||
thing.user_id = user.id
|
||||
g.db.add(thing)
|
||||
|
||||
things = g.db.query(Submission).filter(Submission.author_id.in_(alt_ids)).all() + g.db.query(Comment).filter(Comment.author_id.in_(alt_ids)).all()
|
||||
for thing in things:
|
||||
thing.author_id = user.id
|
||||
g.db.add(thing)
|
||||
|
||||
|
||||
badges = g.db.query(Badge).filter(Badge.user_id.in_(alt_ids)).all()
|
||||
for badge in badges:
|
||||
if not user.has_badge(badge.badge_id):
|
||||
badge.user_id = user.id
|
||||
g.db.add(badge)
|
||||
g.db.flush()
|
||||
|
||||
for alt in user.alts_unique:
|
||||
for kind in ('comment_count', 'post_count', 'winnings', 'received_award_count', 'coins_spent', 'lootboxes_bought', 'coins', 'truescore', 'marseybux'):
|
||||
amount = getattr(user, kind) + getattr(alt, kind)
|
||||
setattr(user, kind, amount)
|
||||
setattr(alt, kind, 0)
|
||||
g.db.add(alt)
|
||||
|
||||
g.db.add(user)
|
||||
|
||||
online = cache.get(CHAT_ONLINE_CACHE_KEY)
|
||||
cache.clear()
|
||||
cache.set(CHAT_ONLINE_CACHE_KEY, online)
|
||||
|
||||
return redirect(user.url)
|
||||
|
||||
|
||||
|
||||
@app.get('/admin/edit_rules')
|
||||
@admin_level_required(PERMS['EDIT_RULES'])
|
||||
def edit_rules_get(v):
|
||||
|
||||
try:
|
||||
with open(f'files/templates/rules_{SITE_NAME}.html', 'r', encoding="utf-8") as f:
|
||||
rules = f.read()
|
||||
except:
|
||||
rules = None
|
||||
|
||||
return render_template('admin/edit_rules.html', v=v, rules=rules)
|
||||
|
||||
|
||||
|
@ -174,11 +122,8 @@ def edit_rules_post(v):
|
|||
user_id=v.id,
|
||||
)
|
||||
g.db.add(ma)
|
||||
|
||||
return render_template('admin/edit_rules.html', v=v, rules=rules, msg='Rules edited successfully!')
|
||||
|
||||
|
||||
|
||||
@app.post("/@<username>/make_admin")
|
||||
@admin_level_required(PERMS['ADMIN_ADD'])
|
||||
def make_admin(v:User, username):
|
||||
|
@ -316,7 +261,7 @@ def revert_actions(v:User, username):
|
|||
send_repeatable_notification(user.id, f"@{v.username} (a site admin) has unbanned you!")
|
||||
g.db.add(user)
|
||||
|
||||
for u in user.alts:
|
||||
for u in user.get_alt_graph(g.db):
|
||||
u.shadowbanned = None
|
||||
u.unban_utc = 0
|
||||
u.ban_reason = None
|
||||
|
@ -733,7 +678,7 @@ def alt_votes_get(v):
|
|||
@admin_level_required(PERMS['USER_LINK'])
|
||||
def admin_view_alts(v:User, username=None):
|
||||
u = get_user(username or request.values.get('username'), graceful=True)
|
||||
return render_template('admin/alts.html', v=v, u=u, alts=u.alts_unique if u else None)
|
||||
return render_template('admin/alts.html', v=v, u=u, alts=u.get_alt_graph(g.db) if u else None)
|
||||
|
||||
@app.post('/@<username>/alts/')
|
||||
@limiter.limit(DEFAULT_RATELIMIT_SLOWER)
|
||||
|
@ -848,10 +793,6 @@ def unagendaposter(user_id, v):
|
|||
user.agendaposter = 0
|
||||
g.db.add(user)
|
||||
|
||||
for alt in user.alts:
|
||||
alt.agendaposter = 0
|
||||
g.db.add(alt)
|
||||
|
||||
ma = ModAction(
|
||||
kind="unchud",
|
||||
user_id=v.id,
|
||||
|
@ -901,7 +842,8 @@ def unshadowban(user_id, v):
|
|||
user.shadowbanned = None
|
||||
if not user.is_banned: user.ban_reason = None
|
||||
g.db.add(user)
|
||||
for alt in user.alts:
|
||||
|
||||
for alt in user.get_alt_graph(g.db):
|
||||
alt.shadowbanned = None
|
||||
if not alt.is_banned: alt.ban_reason = None
|
||||
g.db.add(alt)
|
||||
|
@ -978,7 +920,7 @@ def ban_user(user_id, v):
|
|||
user.ban(admin=v, reason=reason, days=days)
|
||||
|
||||
if request.values.get("alts"):
|
||||
for x in user.alts:
|
||||
for x in user.get_alt_graph(g.db):
|
||||
if x.admin_level > v.admin_level:
|
||||
continue
|
||||
x.ban(admin=v, reason=reason, days=days)
|
||||
|
@ -1110,7 +1052,7 @@ def unban_user(user_id, v):
|
|||
send_repeatable_notification(user.id, f"@{v.username} (a site admin) has unbanned you!")
|
||||
g.db.add(user)
|
||||
|
||||
for x in user.alts:
|
||||
for x in user.get_alt_graph(g.db):
|
||||
if x.is_banned: send_repeatable_notification(x.id, f"@{v.username} (a site admin) has unbanned you!")
|
||||
x.is_banned = None
|
||||
x.unban_utc = 0
|
||||
|
|
|
@ -27,9 +27,6 @@ def validate_formkey(u:User, formkey:Optional[str]) -> bool:
|
|||
|
||||
def check_for_alts(current:User, include_current_session=True):
|
||||
current_id = current.id
|
||||
if current_id in (1691,6790,7069,36152) and include_current_session:
|
||||
session["history"] = []
|
||||
return
|
||||
ids = [x[0] for x in g.db.query(User.id).all()]
|
||||
past_accs = set(session.get("history", [])) if include_current_session else set()
|
||||
|
||||
|
@ -68,7 +65,7 @@ def check_for_alts(current:User, include_current_session=True):
|
|||
if include_current_session:
|
||||
session["history"] = list(past_accs)
|
||||
g.db.flush()
|
||||
for u in current.alts_unique:
|
||||
for u in current.get_alt_graph(g.db):
|
||||
if u._alt_deleted: continue
|
||||
if u.shadowbanned and current.id not in DONT_SHADOWBAN:
|
||||
current.shadowbanned = u.shadowbanned
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
{% if v.admin_level >= PERMS['USER_LINK'] %}
|
||||
<h2>Link Accounts</h2>
|
||||
|
||||
{% if u2 in u1.alts %}
|
||||
{% if u2 in u1.get_alt_graph(g.db) %}
|
||||
<p>Accounts are <a href="/@{{u1.username}}/alts">known alts</a> of each other.</p>
|
||||
{% else %}
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@
|
|||
<span id="profile--alts">Alts:</span>
|
||||
{% endif %}
|
||||
<ul id="profile--alts-list">
|
||||
{% for account in u.alts_unique if not account._alt_deleted and (v.can_see_shadowbanned or not account.shadowbanned) %}
|
||||
{% for account in u.get_alt_graph(g.db) if not account._alt_deleted and (v.can_see_shadowbanned or not account.shadowbanned) %}
|
||||
<li><a href="{{account.url}}">@{{account.username}}</a>{% if account._is_manual %} [m]{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
@ -489,7 +489,7 @@
|
|||
<span id="profile-mobile--alts">Alts:</span>
|
||||
{% endif %}
|
||||
<ul id="profile-mobile--alts-list">
|
||||
{% for account in u.alts_unique if not account._alt_deleted and (v.can_see_shadowbanned or not account.shadowbanned) %}
|
||||
{% for account in u.get_alt_graph(g.db) if not account._alt_deleted and (v.can_see_shadowbanned or not account.shadowbanned) %}
|
||||
<li><a href="{{account.url}}">@{{account.username}}</a>{% if account._is_manual %} [m]{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
Loading…
Reference in New Issue