forked from rDrama/rDrama
account linking improvements (#448)
currently account delinking is very messy and can sometimes just not work we do codey stuff so it's not as bad also we create a pretty page for mops to mop up borked account links * alts: allow proper delinking * fix prev commit * url fix * fix 500 * fixes * :pepodrool: * flag * :pepodrool: redux * sdsdsdsds * correct endpoint * fix html page * alts: only adjust session history if flag is set * fix 500 * allow relinking * fsdsds * :pepodrool: redux * alts: don't fail if an alt isn't history * use postToastSwitch + some API changes * remove unnecessary variables * d-none * delink accounts mod action * fa-link-slash * alts: add form to create alt * remove copied and pasted template * rounded section * UI improvement + fix * \n * fix status * admin: remove duplicate route admin: do a permissions check on 2 pages that need it admin: set the manual flag for manually flagged alts * variable change * fix 500 * alts * add shadowban icon to alt link tool * shadowbanned tooltip * add user info section * fix 500, remove unnecessary form, and add alt votes button * trans and also link to page * margin * sdsdsd * stop the count * fix prev commit * with ctx * plural * alts * don't show shadowbanned users to those who can't see them this is... extremely rare and won't ever be seen in production however if perms were ever rearranged in the future, this keeps permissions correct * shadowban check in alt list * let shadow realm enthusiasts see shadowban alts * sdsdsds * test * be graceful where needed * sdsdsdsds * alts: don't allow adding the same account alts: clarify wording * rename and reorder on admin panel * EOL * remove frankly unnecessary check * try with a set * test * Revert "try with a set" This reverts commit 72be353fba5ffa39b37590cc5d3bf584c94ee06e. * Revert "Revert "try with a set"" This reverts commit 81e41890a192e8b46d0463477998e905fddf56ba. * Revert "Revert "Revert "try with a set""" This reverts commit be51592135a3c09848f993f0154bd2ac862ae505. * clean up testmaster
parent
65e555692a
commit
c9ecb5d535
|
@ -4084,6 +4084,13 @@ ul.comment-section {
|
|||
.rounded {
|
||||
border-radius: 0.35rem;
|
||||
}
|
||||
|
||||
.rounded-section {
|
||||
margin-bottom: 3rem;
|
||||
border: .1px solid var(--gray-400);
|
||||
border-radius: .35rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
.rounded-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
@ -5920,6 +5927,7 @@ g {
|
|||
.fa-knife-kitchen:before{content:"\f6f5"}
|
||||
.fa-lights-holiday:before{content:"\f7b2"}
|
||||
.fa-link:before{content:"\f0c1"}
|
||||
.fa-link-slash:before{content:"\f127"}
|
||||
.fa-lock:before{content:"\f023"}
|
||||
.fa-lock-alt:before{content:"\f30d"}
|
||||
.fa-search:before{content:"\f002"}
|
||||
|
|
|
@ -9,6 +9,7 @@ class Alt(Base):
|
|||
user2 = Column(Integer, ForeignKey("users.id"), primary_key=True)
|
||||
is_manual = Column(Boolean, default=False)
|
||||
created_utc = Column(Integer)
|
||||
deleted = Column(Boolean, default=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
|
||||
|
|
|
@ -226,6 +226,11 @@ ACTIONTYPES = {
|
|||
"icon": 'fa-link',
|
||||
"color": 'bg-success'
|
||||
},
|
||||
'delink_accounts': {
|
||||
"str": 'delinked {self.target_link}',
|
||||
"icon": 'fa-link-slash',
|
||||
"color": 'bg-danger'
|
||||
},
|
||||
'make_admin': {
|
||||
"str": 'made {self.target_link} an admin',
|
||||
"icon": 'fa-user-crown',
|
||||
|
|
|
@ -739,7 +739,6 @@ class User(Base):
|
|||
@property
|
||||
@lazy
|
||||
def alts(self):
|
||||
|
||||
subq = g.db.query(Alt).filter(
|
||||
or_(
|
||||
Alt.user1 == self.id,
|
||||
|
@ -764,6 +763,8 @@ class User(Base):
|
|||
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
|
||||
|
|
|
@ -27,7 +27,7 @@ def get_id(username:str, graceful=False) -> Optional[int]:
|
|||
|
||||
return user[0]
|
||||
|
||||
def get_user(username:str, v:Optional[User]=None, graceful=False, include_blocks=False, include_shadowbanned=True) -> Optional[User]:
|
||||
def get_user(username:Optional[str], v:Optional[User]=None, graceful=False, include_blocks=False, include_shadowbanned=True) -> Optional[User]:
|
||||
if not username:
|
||||
if graceful: return None
|
||||
abort(404)
|
||||
|
|
|
@ -636,7 +636,6 @@ def users_list(v):
|
|||
@app.get("/admin/alt_votes")
|
||||
@admin_level_required(PERMS['VIEW_ALT_VOTES'])
|
||||
def alt_votes_get(v):
|
||||
|
||||
u1 = request.values.get("u1")
|
||||
u2 = request.values.get("u2")
|
||||
|
||||
|
@ -738,35 +737,78 @@ def alt_votes_get(v):
|
|||
data=data
|
||||
)
|
||||
|
||||
|
||||
@app.post("/admin/link_accounts")
|
||||
@app.get("/admin/alts/")
|
||||
@app.get("/@<username>/alts/")
|
||||
@limiter.limit(DEFAULT_RATELIMIT_SLOWER)
|
||||
@admin_level_required(PERMS['USER_LINK'])
|
||||
def admin_link_accounts(v):
|
||||
u1 = get_account(request.values.get("u1")).id
|
||||
u2 = get_account(request.values.get("u2")).id
|
||||
def admin_view_alts(v, 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)
|
||||
|
||||
new_alt = Alt(
|
||||
user1=u1,
|
||||
user2=u2,
|
||||
is_manual=True
|
||||
@app.post('/@<username>/alts/')
|
||||
@limiter.limit(DEFAULT_RATELIMIT_SLOWER)
|
||||
@admin_level_required(PERMS['USER_LINK'])
|
||||
def admin_add_alt(v, username):
|
||||
user1 = get_user(username)
|
||||
user2 = get_user(request.values.get('other_username'))
|
||||
if user1.id == user2.id: abort(400, "Can't add the same account as alts of each other")
|
||||
|
||||
deleted = request.values.get('deleted', False, bool) or False
|
||||
ids = [user1.id, user2.id]
|
||||
a = g.db.query(Alt).filter(Alt.user1.in_(ids), Alt.user2.in_(ids)).one_or_none()
|
||||
if a: abort(409, f"@{user1.username} and @{user2.username} are already known {'linked' if not a.deleted else 'delinked'} alts")
|
||||
a = Alt(
|
||||
user1=user1.id,
|
||||
user2=user2.id,
|
||||
manual=True,
|
||||
deleted=deleted
|
||||
)
|
||||
|
||||
g.db.add(new_alt)
|
||||
g.db.add(a)
|
||||
g.db.flush()
|
||||
|
||||
check_for_alts(g.db.get(User, u1), include_current_session=False)
|
||||
check_for_alts(g.db.get(User, u2), include_current_session=False)
|
||||
check_for_alts(user1, include_current_session=False)
|
||||
check_for_alts(user2, include_current_session=False)
|
||||
|
||||
word = 'Delinked' if deleted else 'Linked'
|
||||
ma_word = 'delink' if deleted else 'link'
|
||||
note = f'from {user2.id}' if deleted else f'with {user2.id}'
|
||||
ma = ModAction(
|
||||
kind=f"{ma_word}_accounts",
|
||||
user_id=v.id,
|
||||
target_user_id=user1.id,
|
||||
_note=note
|
||||
)
|
||||
g.db.add(ma)
|
||||
return {"message": f"{word} @{user1.username} and @{user2.username} successfully!"}
|
||||
|
||||
@app.route('/@<username>/alts/<int:other>/deleted', methods=["PUT", "DELETE"])
|
||||
@limiter.limit(DEFAULT_RATELIMIT_SLOWER)
|
||||
@admin_level_required(PERMS['USER_LINK'])
|
||||
def admin_delink_relink_alt(v, username, other):
|
||||
is_delinking = request.method == 'PUT' # we're adding the 'deleted' state if a PUT request
|
||||
user1 = get_user(username)
|
||||
user2 = get_account(other)
|
||||
ids = [user1.id, user2.id]
|
||||
a = g.db.query(Alt).filter(Alt.user1.in_(ids), Alt.user2.in_(ids)).one_or_none()
|
||||
if not a: abort(404)
|
||||
a.deleted = is_delinking
|
||||
g.db.add(a)
|
||||
g.db.flush()
|
||||
check_for_alts(user1, include_current_session=False)
|
||||
check_for_alts(user2, include_current_session=False)
|
||||
word = 'Delinked' if is_delinking else 'Relinked'
|
||||
ma_word = 'delink' if is_delinking else 'link'
|
||||
note = f'from {user2.id}' if is_delinking else f'with {user2.id} (relinked)'
|
||||
|
||||
ma = ModAction(
|
||||
kind="link_accounts",
|
||||
kind=f"{ma_word}_accounts",
|
||||
user_id=v.id,
|
||||
target_user_id=u1,
|
||||
_note=f'with {u2}'
|
||||
target_user_id=user1.id,
|
||||
_note=note
|
||||
)
|
||||
g.db.add(ma)
|
||||
|
||||
return redirect(f"/admin/alt_votes?u1={get_account(u1).username}&u2={get_account(u2).username}")
|
||||
return {"message": f"{word} @{user1.username} and @{user2.username} successfully!"}
|
||||
|
||||
|
||||
@app.get("/admin/removed/posts")
|
||||
|
|
|
@ -48,20 +48,24 @@ def check_for_alts(current:User, include_current_session=True):
|
|||
add_alt(past_id, current_id)
|
||||
other_alts = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).all()
|
||||
for a in other_alts:
|
||||
if a.user1 != past_id:
|
||||
add_alt(a.user1, past_id)
|
||||
if a.user1 != current_id:
|
||||
add_alt(a.user1, current_id)
|
||||
if a.user2 != past_id:
|
||||
add_alt(a.user2, past_id)
|
||||
if a.user2 != current_id:
|
||||
add_alt(a.user2, current_id)
|
||||
if a.deleted:
|
||||
if include_current_session:
|
||||
try: session["history"].remove(a.user1)
|
||||
except: pass
|
||||
try: session["history"].remove(a.user2)
|
||||
except: pass
|
||||
continue # don't propagate deleted alt links
|
||||
if a.user1 != past_id: add_alt(a.user1, past_id)
|
||||
if a.user1 != current_id: add_alt(a.user1, current_id)
|
||||
if a.user2 != past_id: add_alt(a.user2, past_id)
|
||||
if a.user2 != current_id: add_alt(a.user2, current_id)
|
||||
|
||||
past_accs.add(current_id)
|
||||
if include_current_session:
|
||||
session["history"] = list(past_accs)
|
||||
g.db.flush()
|
||||
for u in current.alts_unique:
|
||||
if u._alt_deleted: continue
|
||||
if u.shadowbanned:
|
||||
current.shadowbanned = u.shadowbanned
|
||||
if not current.is_banned: current.ban_reason = u.ban_reason
|
||||
|
@ -369,12 +373,11 @@ def sign_up_post(v):
|
|||
send_verification_email(new_user)
|
||||
|
||||
|
||||
check_for_alts(new_user)
|
||||
|
||||
send_notification(new_user.id, WELCOME_MSG)
|
||||
|
||||
session["lo_user"] = new_user.id
|
||||
|
||||
check_for_alts(new_user)
|
||||
send_notification(new_user.id, WELCOME_MSG)
|
||||
|
||||
if SIGNUP_FOLLOW_ID:
|
||||
signup_autofollow = get_account(SIGNUP_FOLLOW_ID)
|
||||
new_follow = Follow(user_id=new_user.id, target_id=signup_autofollow.id)
|
||||
|
|
|
@ -145,7 +145,7 @@ def log(v):
|
|||
actions = actions.filter(ModAction.kind.notin_([
|
||||
"shadowban","unshadowban",
|
||||
"mod_mute_user","mod_unmute_user",
|
||||
"link_accounts",
|
||||
"link_accounts","delink_accounts",
|
||||
]))
|
||||
|
||||
if admin_id:
|
||||
|
|
|
@ -69,7 +69,12 @@
|
|||
<h4>Safety</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/banned_domains">Banned Domains</a></li>
|
||||
{% if v.admin_level >= PERMS['USER_LINK'] %}
|
||||
<li><a href="/admin/alts/">View and Link Alts</a></li>
|
||||
{% endif %}
|
||||
{% if v.admin_level >= PERMS['VIEW_ALT_VOTES'] %}
|
||||
<li><a href="/admin/alt_votes">Multi Vote Analysis</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
{% if FEATURES['BADGES'] or FEATURES['AWARDS'] -%}
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{SITE_NAME}}</title>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetitle %}Alt Vote Analysis{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h5 class="mt-3">Vote Info</h5>
|
||||
|
||||
<form action="/admin/alt_votes" method="get" class="mb-6">
|
||||
<form action="" method="get" class="mb-6">
|
||||
<label for="link-input">Usernames</label>
|
||||
<input autocomplete="off" id="link-input" type="text" class="form-control mb-2" name="u1" value="{{u1.username if u1 else ''}}" placeholder="User 1">
|
||||
<input autocomplete="off" id="link-input" type="text" class="form-control mb-2" name="u2" value="{{u2.username if u2 else ''}}" placeholder="User 2">
|
||||
|
@ -17,12 +11,7 @@
|
|||
</form>
|
||||
|
||||
{% if u1 and u2 %}
|
||||
|
||||
|
||||
<h2>Analysis</h2>
|
||||
|
||||
|
||||
|
||||
<div class="overflow-x-auto"><table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
|
@ -32,7 +21,6 @@
|
|||
<th>@{{u2.username}} only (% unique)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr>
|
||||
<td><b>Post Upvotes</b></td>
|
||||
<td>{{data['u1_only_post_ups']}} ({{data['u1_post_ups_unique']}}%)</td>
|
||||
|
@ -58,27 +46,18 @@
|
|||
<td>{{data['u2_only_comment_downs']}} ({{data['u2_comment_downs_unique']}}%)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{% if v.admin_level >= PERMS['USER_LINK'] %}
|
||||
<h2>Link Accounts</h2>
|
||||
|
||||
{% if u2 in u1.alts %}
|
||||
<p>Accounts are known alts of eachother.</p>
|
||||
<p>Accounts are <a href="/@{{u1.username}}/alts">known alts</a> of each other.</p>
|
||||
{% else %}
|
||||
|
||||
<p>Two accounts controlled by different people should have most uniqueness percentages at or above 70-80%</p>
|
||||
<p>A sockpuppet account will have its uniqueness percentages significantly lower.</p>
|
||||
|
||||
<button type="button" class="btn btn-secondary" onclick="document.getElementById('linkbtn').classList.toggle('d-none');">Link Accounts</button>
|
||||
<form action="/admin/link_accounts" method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
<input type="hidden" name="u1" value="{{u1.id}}">
|
||||
<input type="hidden" name="u2" value="{{u2.id}}">
|
||||
<input type="submit" onclick="disable(this)" id="linkbtn" class="btn btn-primary d-none" value="Confirm Link: {{u1.username}} and {{u2.username}}">
|
||||
</form>
|
||||
|
||||
<button class="btn btn-danger" data-click="postToastReload(this,'/@{{u1.username}}/alts/?other_username={{u2.username}}')" onclick="areyousure(this)">Link {{u1.username}} and {{u2.username}}</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
{% extends "settings2.html" %}
|
||||
{% block content %}
|
||||
{% if u %}
|
||||
<h5 class="mt-3">Alts for <a href="/@{{u.username}}">@{{u.username}}</a></h5>
|
||||
{% else %}
|
||||
<h5 class="mt-3">Alts</h5>
|
||||
{% endif %}
|
||||
{%- import 'util/helpers.html' as help -%}
|
||||
<section class="username-input-section mb-3">
|
||||
<form action="/admin/alts" method="get">
|
||||
<label for="link-input">Username</label>
|
||||
<input autocomplete="off" id="link-input" type="text" class="form-control mb-2" name="username" value="{{u.username if u else ''}}" placeholder="Username">
|
||||
<input type="submit" onclick="disable(this)" value="Submit" class="btn btn-primary">
|
||||
</form>
|
||||
</section>
|
||||
{% if u %}
|
||||
{% set count=alts|length %}
|
||||
<section class="userinfo-section">
|
||||
<p><a href="/@{{u.username}}">@{{u.username}}</a> created their account {{u.created_utc|timestamp}} and has {{count}} known alt{{help.plural(count)}}.<br>
|
||||
They are {% if not u.is_suspended_permanently %}not {% endif %}permanently banned{% if v.admin_level >= PERMS['USER_SHADOWBAN'] %} and they are {% if not u.shadowbanned %}not {% endif %}shadowbanned{% endif %}.</p>
|
||||
</section>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Name</th>
|
||||
<th>Account Created</th>
|
||||
<th>Alt Link Created</th>
|
||||
<th>Manual</th>
|
||||
<th>Delinked</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in alts %}
|
||||
<tr>
|
||||
<td>{{loop.index}}</td>
|
||||
<td>{% include "admin/shadowbanned_tooltip.html" %}{% include "user_in_table.html" %}</td>
|
||||
<td>{{user.created_utc|timestamp}}</td>
|
||||
<td>{{user._alt_created_utc|timestamp}}</td>
|
||||
<td>{{user._is_manual}}</td>
|
||||
<td>{{user._alt_deleted}}</td>
|
||||
<td>
|
||||
<button type="button" id="delink-alt-{{u.id}}-{{user.id}}" class="btn btn-danger {% if user._alt_deleted %}d-none{% endif %}" onclick="postToastSwitch(this,'/@{{u.username}}/alts/{{user.id}}/deleted', this.id, 'relink-alt-{{u.id}}-{{user.id}}', 'd-none', null, 'PUT')">Delink</button>
|
||||
<button type="button" id="relink-alt-{{u.id}}-{{user.id}}" class="btn btn-danger {% if not user._alt_deleted %}d-none{% endif %}" onclick="postToastSwitch(this,'/@{{u.username}}/alts/{{user.id}}/deleted', this.id, 'delink-alt-{{u.id}}-{{user.id}}', 'd-none', null, 'DELETE')">Relink</button>
|
||||
<a class="btn btn-secondary" href="/@{{user.username}}/alts">Alts</a>
|
||||
{% if v.admin_level >= PERMS['VIEW_ALT_VOTES'] %}
|
||||
<a class="btn btn-secondary" href="/admin/alt_votes/?u1={{u.username}}&u2={{user.username}}">Alt Votes</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
<section id="add-alt" class="rounded rounded-section p-3">
|
||||
<h5>Add Alt</h5>
|
||||
<p>This tool allows you to add an alt either linked or delinked.<br>
|
||||
Adding linked will link the two alts together manually, while adding delinked will attempt to delink alts whereever possible.<br>
|
||||
You're on your own for reversing any propagation though.
|
||||
</p>
|
||||
<form onsubmit="return false;">
|
||||
<label for="link-input-other">Other Username</label>
|
||||
<input autocomplete="off" id="link-input-other" type="text" class="form-control mb-2" name="other_username" placeholder="Other Username">
|
||||
<button id="add-alt-form-link" class="btn btn-danger mr-3" data-click="submitAddAlt(this)" onclick="areyousure(this)">Add Alt Linked</button>
|
||||
<button id="add-alt-form-delink" class="btn btn-danger" data-click="submitAddAlt(this)" onclick="areyousure(this)">Add Alt Delinked</button>
|
||||
</form>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<script type="application/javascript">
|
||||
function submitAddAlt(element) {
|
||||
if (!element || !element.form) return;
|
||||
const isLinking = element.id == 'add-alt-form-link';
|
||||
const otherElement = isLinking ? document.getElementById('add-alt-form-delink') : document.getElementById('add-alt-form-link');
|
||||
if (!otherElement) return;
|
||||
element.disabled = true;
|
||||
otherElement.disabled = true;
|
||||
element.classList.add('disabled');
|
||||
otherElement.classList.add('disabled');
|
||||
const form = new FormData();
|
||||
if (!isLinking) form.append('deleted', 'true');
|
||||
form.append('other_username', document.getElementById('link-input-other').value);
|
||||
const xhr = createXhrWithFormKey('/@{{u.username}}/alts/', 'POST', form);
|
||||
xhr[0].onload = function() {
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(xhr[0].response);
|
||||
}
|
||||
catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
if (xhr[0].status >= 200 && xhr[0].status < 300) {
|
||||
showToast(true, getMessageFromJsonData(true, data));
|
||||
window.location.reload();
|
||||
} else {
|
||||
showToast(false, getMessageFromJsonData(false, data));
|
||||
element.disabled = false;
|
||||
otherElement.disabled = false;
|
||||
element.classList.remove('disabled');
|
||||
otherElement.classList.remove('disabled');
|
||||
}
|
||||
}
|
||||
xhr[0].send(xhr[1]);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -0,0 +1 @@
|
|||
{% if v and v.admin_level >= PERMS['USER_SHADOWBAN'] and user.shadowbanned %}<i class="fas fa-user-times text-admin" data-bs-toggle="tooltip" data-bs-placement="bottom" title='Shadowbanned by @{{user.shadowbanned}}{% if user.ban_reason %} for "{{user.ban_reason}}"{% endif %}'></i>{% endif %}
|
|
@ -230,9 +230,13 @@
|
|||
<p id="profile--info--private">User has private mode enabled</p>
|
||||
{% endif %}
|
||||
{% if v and (v.admin_level >= PERMS['VIEW_ALTS'] or v.alt) %}
|
||||
{% if v.admin_level >= PERMS['USER_LINK'] %}
|
||||
<span id="profile--alts"><a href="/@{{u.username}}/alts">Alts</a>:</span>
|
||||
{% else %}
|
||||
<span id="profile--alts">Alts:</span>
|
||||
{% endif %}
|
||||
<ul id="profile--alts-list">
|
||||
{% for account in u.alts_unique %}
|
||||
{% for account in u.alts_unique 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>
|
||||
|
@ -469,9 +473,13 @@
|
|||
<p id="profile-mobile--info--private">User has private mode enabled</p>
|
||||
{% endif %}
|
||||
{% if v and (v.admin_level >= PERMS['VIEW_ALTS'] or v.alt) %}
|
||||
{% if v.admin_level >= PERMS['USER_LINK'] %}
|
||||
<span id="profile-mobile--alts"><a href="/@{{u.username}}/alts">Alts</a>:</span>
|
||||
{% else %}
|
||||
<span id="profile-mobile--alts">Alts:</span>
|
||||
{% endif %}:
|
||||
<ul id="profile-mobile--alts-list">
|
||||
{% for account in u.alts_unique %}
|
||||
{% for account in u.alts_unique 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