add web
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
{% extends '__layout__.html' %}
|
||||
{% block title %}{{profile.username}}{% endblock %}
|
||||
{% block head %}
|
||||
<link href="/static/css/profile.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
|
||||
<style>
|
||||
.text-secondary {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.border {
|
||||
border-color: rgb(60,60,60) !important;
|
||||
}
|
||||
.nav-link {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.nav-link.active {
|
||||
color: rgb(255, 255, 255) !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="main">
|
||||
<div class="container" style="max-width: 800px;">
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="/users/{{profile.id}}/profile">
|
||||
<img src="/Thumbs/Head.ashx?x=100&y=100&userId={{profile.id}}" width="80px" height="80px" class="rounded-2">
|
||||
</a>
|
||||
<h2 class="ms-3">{{profile.username}}'s Followers</h2>
|
||||
</div>
|
||||
<nav class="mt-2 rounded-top" style="background-color: rgb(40, 40, 40) !important;overflow: hidden;">
|
||||
<div class="nav nav-underline nav-fill" id="nav-tab" role="tablist">
|
||||
<a class="nav-link" type="button" role="tab" aria-selected="false" href="/users/{{profile.id}}/friends">Friends</a>
|
||||
<a class="nav-link" type="button" role="tab" aria-selected="true" href="/users/{{profile.id}}/following">Following</a>
|
||||
<a class="nav-link active" type="button" role="tab" aria-selected="true">Followers</a>
|
||||
{% if profile.id == currentuser.id %}
|
||||
<a class="nav-link" type="button" role="tab" aria-selected="true" href="/users/{{profile.id}}/requests">Requests{% if currentuser.friend_requests > 0: %}<span class="badge text-bg-danger ms-1">{{currentuser.friend_requests}}</span>{%endif%}</a>
|
||||
{%endif%}
|
||||
</div>
|
||||
</nav>
|
||||
<div>
|
||||
<h4 class="m-0 mt-2">Followers ( {{FollowCount}} )</h4>
|
||||
<div class="row mt-2">
|
||||
{% for following in FollowUsers: %}
|
||||
<div class="col-6 col-sm-4 mb-2">
|
||||
<div class="bg-dark border p-2 rounded d-flex">
|
||||
<a href="/users/{{following.id}}/profile">
|
||||
<img src="/Thumbs/Head.ashx?x=100&y=100&userId={{following.id}}" width="80px" height="80px" class="rounded-2" alt="{{following.username}}">
|
||||
</a>
|
||||
<a class="text-white text-decoration-none ms-2" href="/users/{{following.id}}/profile">{{following.username}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{%endfor%}
|
||||
</div>
|
||||
{% if len(FollowUsers) <= 0: %}
|
||||
<p class="w-100 text-secondary text-center text-white mt-4 mb-4">No results found</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex mt-2 align-items-center">
|
||||
<a class="ms-auto text-decoration-none {%if not isTherePreviousPage:%}text-secondary{%endif%}" {%if isTherePreviousPage: %}href="/users/{{profile.id}}/followers?page={{PageNumber - 1}}{%endif%}">Previous</a>
|
||||
<p class="m-0 ms-2 me-2 text-white">Page {{PageNumber}} of {{TotalPages}}</p>
|
||||
<a class="me-auto text-decoration-none {%if not isThereNextPage: %}text-secondary{%endif%}" {%if isThereNextPage: %}href="/users/{{profile.id}}/followers?page={{PageNumber + 1}}{%endif%}">Next</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,64 @@
|
||||
{% extends '__layout__.html' %}
|
||||
{% block title %}{{profile.username}}{% endblock %}
|
||||
{% block head %}
|
||||
<link href="/static/css/profile.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
|
||||
<style>
|
||||
.text-secondary {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.border {
|
||||
border-color: rgb(60,60,60) !important;
|
||||
}
|
||||
.nav-link {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.nav-link.active {
|
||||
color: rgb(255, 255, 255) !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="main">
|
||||
<div class="container" style="max-width: 800px;">
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="/users/{{profile.id}}/profile">
|
||||
<img src="/Thumbs/Head.ashx?x=100&y=100&userId={{profile.id}}" width="80px" height="80px" class="rounded-2">
|
||||
</a>
|
||||
<h2 class="ms-3">{{profile.username}}'s Following</h2>
|
||||
</div>
|
||||
<nav class="mt-2 rounded-top" style="background-color: rgb(40, 40, 40) !important;overflow: hidden;">
|
||||
<div class="nav nav-underline nav-fill" id="nav-tab" role="tablist">
|
||||
<a class="nav-link" type="button" role="tab" aria-selected="false" href="/users/{{profile.id}}/friends">Friends</a>
|
||||
<a class="nav-link active" type="button" role="tab" aria-selected="true">Following</a>
|
||||
<a class="nav-link" type="button" role="tab" aria-selected="false" href="/users/{{profile.id}}/followers">Followers</a>
|
||||
{% if profile.id == currentuser.id %}
|
||||
<a class="nav-link" type="button" role="tab" aria-selected="true" href="/users/{{profile.id}}/requests">Requests{% if currentuser.friend_requests > 0: %}<span class="badge text-bg-danger ms-1">{{currentuser.friend_requests}}</span>{%endif%}</a>
|
||||
{%endif%}
|
||||
</div>
|
||||
</nav>
|
||||
<div>
|
||||
<h4 class="m-0 mt-2">Following ( {{FollowCount}} )</h4>
|
||||
<div class="row mt-2">
|
||||
{% for following in FollowUsers: %}
|
||||
<div class="col-6 col-sm-4 mb-2">
|
||||
<div class="bg-dark border p-2 rounded d-flex">
|
||||
<a href="/users/{{following.id}}/profile">
|
||||
<img src="/Thumbs/Head.ashx?x=100&y=100&userId={{following.id}}" width="80px" height="80px" class="rounded-2" alt="{{following.username}}">
|
||||
</a>
|
||||
<a class="text-white text-decoration-none ms-2" href="/users/{{following.id}}/profile">{{following.username}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{%endfor%}
|
||||
</div>
|
||||
{% if len(FollowUsers) <= 0: %}
|
||||
<p class="w-100 text-secondary text-center text-white mt-4 mb-4">No results found</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex mt-2 align-items-center">
|
||||
<a class="ms-auto text-decoration-none {%if not isTherePreviousPage:%}text-secondary{%endif%}" {%if isTherePreviousPage: %}href="/users/{{profile.id}}/following?page={{PageNumber - 1}}{%endif%}">Previous</a>
|
||||
<p class="m-0 ms-2 me-2 text-white">Page {{PageNumber}} of {{TotalPages}}</p>
|
||||
<a class="me-auto text-decoration-none {%if not isThereNextPage: %}text-secondary{%endif%}" {%if isThereNextPage: %}href="/users/{{profile.id}}/following?page={{PageNumber + 1}}{%endif%}">Next</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,74 @@
|
||||
{% extends '__layout__.html' %}
|
||||
{% block title %}{{profile.username}}{% endblock %}
|
||||
{% block head %}
|
||||
<link href="/static/css/profile.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
|
||||
<style>
|
||||
.text-secondary {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.border {
|
||||
border-color: rgb(60,60,60) !important;
|
||||
}
|
||||
.nav-link {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.nav-link.active {
|
||||
color: rgb(255, 255, 255) !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="main">
|
||||
<div class="container" style="max-width: 800px;">
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="/users/{{profile.id}}/profile">
|
||||
<img src="/Thumbs/Head.ashx?x=100&y=100&userId={{profile.id}}" width="80px" height="80px" class="rounded-2">
|
||||
</a>
|
||||
<h2 class="ms-3">{{profile.username}}'s Friends</h2>
|
||||
</div>
|
||||
<nav class="mt-2 rounded-top" style="background-color: rgb(40, 40, 40) !important;overflow: hidden;">
|
||||
<div class="nav nav-underline nav-fill" id="nav-tab" role="tablist">
|
||||
<a class="nav-link active" type="button" role="tab" aria-selected="true">Friends</a>
|
||||
<a class="nav-link" type="button" role="tab" aria-selected="false" href="/users/{{profile.id}}/following">Following</a>
|
||||
<a class="nav-link" type="button" role="tab" aria-selected="false" href="/users/{{profile.id}}/followers">Followers</a>
|
||||
{% if profile.id == currentuser.id %}
|
||||
<a class="nav-link" type="button" role="tab" aria-selected="true" href="/users/{{profile.id}}/requests">Requests{% if currentuser.friend_requests > 0: %}<span class="badge text-bg-danger ms-1">{{currentuser.friend_requests}}</span>{%endif%}</a>
|
||||
{%endif%}
|
||||
</div>
|
||||
</nav>
|
||||
<div>
|
||||
<h4 class="m-0 mt-2">Friends ( {{FriendCount}} )</h4>
|
||||
<div class="row mt-2">
|
||||
{% for friend in FriendUsers: %}
|
||||
<div class="col-6 col-sm-4 mb-2">
|
||||
<div class="bg-dark border p-2 rounded d-flex position-relative">
|
||||
<a href="/users/{{friend.id}}/profile">
|
||||
<img src="/Thumbs/Head.ashx?x=100&y=100&userId={{friend.id}}" width="80px" height="80px" class="rounded-2" alt="{{friend.username}}">
|
||||
</a>
|
||||
<a class="text-white text-decoration-none ms-2" style="font-size: 14px;" href="/users/{{friend.id}}/profile">{{friend.username}}</a>
|
||||
{% if currentuser.id == profile.id %}
|
||||
<div class="dropdown d-flex justify-content-end position-absolute" style="top: 0; right: 0;">
|
||||
<button class="btn" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end p-1">
|
||||
<li><form action="/users/{{friend.id}}/unfriend?redirect=0" method="post"><input type="hidden" name="csrf_token" value="{{csrf_token()}}"><button class="dropdown-item" type="submit">Unfriend</button></form></li>
|
||||
</ul>
|
||||
</div>
|
||||
{%endif%}
|
||||
</div>
|
||||
</div>
|
||||
{%endfor%}
|
||||
</div>
|
||||
{% if len(FriendUsers) <= 0: %}
|
||||
<p class="w-100 text-secondary text-center text-white mt-4 mb-4">No results found</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex mt-2 align-items-center">
|
||||
<a class="ms-auto text-decoration-none {%if not isTherePreviousPage:%}text-secondary{%endif%}" {%if isTherePreviousPage: %}href="/users/{{profile.id}}/friends?page={{PageNumber - 1}}{%endif%}">Previous</a>
|
||||
<p class="m-0 ms-2 me-2 text-white">Page {{PageNumber}} of {{TotalPages}}</p>
|
||||
<a class="me-auto text-decoration-none {%if not isThereNextPage: %}text-secondary{%endif%}" {%if isThereNextPage: %}href="/users/{{profile.id}}/friends?page={{PageNumber + 1}}{%endif%}">Next</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,183 @@
|
||||
{% extends '__layout__.html' %}
|
||||
{% block title %}{{profile.username}} - Inventory{% endblock %}
|
||||
{% block head %}
|
||||
<link href="/static/css/profile.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
|
||||
<style>
|
||||
.text-secondary {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.category-list a {
|
||||
background-color: rgb(31,31,31);
|
||||
color: rgb(200, 200, 200);
|
||||
padding: 8px;
|
||||
transition: 0.1s;
|
||||
box-shadow: 0px 0px 10px 0px rgb(0,0,0,0.5);
|
||||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
display: block;
|
||||
border-left: 0px solid;
|
||||
}
|
||||
.category-list a:hover {
|
||||
border-left: 5px solid rgb(160, 160, 160);
|
||||
transition: 0.1s;
|
||||
color: rgb(255,255,255);
|
||||
}
|
||||
.category-list a.selected {
|
||||
border-left: 5px solid rgb(255,255,255);
|
||||
transition: 0.1s;
|
||||
color: rgb(255,255,255);
|
||||
}
|
||||
|
||||
.item-results a {
|
||||
text-decoration: none !important;
|
||||
color: inherit;
|
||||
}
|
||||
.text-tickets {
|
||||
color: rgb(224, 224, 60) !important;
|
||||
font-size: small;
|
||||
font-weight: 600;
|
||||
}
|
||||
.text-robux {
|
||||
color: rgb(26, 212, 103) !important;
|
||||
font-size: small;
|
||||
font-weight: 600;
|
||||
}
|
||||
.text-limited {
|
||||
color: rgb(255,255,255) !important;
|
||||
background-color: rgb(9, 136, 62);
|
||||
width: fit-content;
|
||||
padding-left: 10px;
|
||||
font-size: 10px;
|
||||
border-top-right-radius: 5px;
|
||||
margin: 0;
|
||||
}
|
||||
.text-limitedu {
|
||||
color: rgb(0,0,0) !important;
|
||||
background-color: rgb(245, 245, 52);
|
||||
padding-right: 3px;
|
||||
font-weight: 800;
|
||||
border-top-right-radius: 5px;
|
||||
padding-left: 3px;
|
||||
}
|
||||
.text-remaining {
|
||||
color: rgb(197, 21, 21) !important;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="main">
|
||||
<div class="container" style="max-width: 1200px;min-width: 800px;">
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="/users/{{profile.id}}/profile">
|
||||
<img src="/Thumbs/Head.ashx?x=100&y=100&userId={{profile.id}}" width="80px" height="80px" class="rounded-2">
|
||||
</a>
|
||||
<h2 class="ms-3">{{profile.username}}'s Inventory</h2>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<div class="p-1 category-list" style="width: 20%;">
|
||||
<a href="/users/{{profile.id}}/inventory?category=0" class="{% if CategoryIndex == 0 %}selected{%endif%}">
|
||||
Accessories
|
||||
</a>
|
||||
<a href="/users/{{profile.id}}/inventory?category=1" class="{% if CategoryIndex == 1 %}selected{%endif%}">
|
||||
Audio
|
||||
</a>
|
||||
<a href="/users/{{profile.id}}/inventory?category=10" class="{% if CategoryIndex == 10 %}selected{%endif%}">
|
||||
Gears
|
||||
</a>
|
||||
<a href="/users/{{profile.id}}/inventory?category=3" class="{% if CategoryIndex == 3 %}selected{%endif%}">
|
||||
Faces
|
||||
</a>
|
||||
<a href="/users/{{profile.id}}/inventory?category=4" class="{% if CategoryIndex == 4 %}selected{%endif%}">
|
||||
Heads
|
||||
</a>
|
||||
<a href="/users/{{profile.id}}/inventory?category=5" class="{% if CategoryIndex == 5 %}selected{%endif%}">
|
||||
Hats
|
||||
</a>
|
||||
<a href="/users/{{profile.id}}/inventory?category=6" class="{% if CategoryIndex == 6 %}selected{%endif%}">
|
||||
Passes
|
||||
</a>
|
||||
<a href="/users/{{profile.id}}/inventory?category=2" class="{% if CategoryIndex == 2 %}selected{%endif%}">
|
||||
Packages
|
||||
</a>
|
||||
<a href="/users/{{profile.id}}/inventory?category=7" class="{% if CategoryIndex == 7 %}selected{%endif%}">
|
||||
T-Shirt
|
||||
</a>
|
||||
<a href="/users/{{profile.id}}/inventory?category=8" class="{% if CategoryIndex == 8 %}selected{%endif%}">
|
||||
Shirts
|
||||
</a>
|
||||
<a href="/users/{{profile.id}}/inventory?category=9" class="{% if CategoryIndex == 9 %}selected{%endif%}">
|
||||
Pants
|
||||
</a>
|
||||
<a href="/users/{{profile.id}}/inventory?category=11" class="{% if CategoryIndex == 11 %}selected{%endif%}">
|
||||
Badges
|
||||
</a>
|
||||
</div>
|
||||
<div class="p-1 ps-3" style="width: 80%;">
|
||||
<h3 class="m-0 mb-1">{{CategoryName}}</h3>
|
||||
<div class="row item-results">
|
||||
{% for item in SearchQuery %}
|
||||
<div class="col-md-2 col-3 p-1">
|
||||
<a href="{% if CategoryIndex != 11 %}/catalog/{{item.asset.id}}/{%else%}/badges/{{item.badge_id}}/{%endif%}">
|
||||
<div class="rounded item-card p-2">
|
||||
<div class="border rounded overflow-hidden position-relative">
|
||||
<img src="/Thumbs/Asset.ashx?x=180&y=180&assetId={% if CategoryIndex != 11 %}{{item.asset.id}}{%else%}{{item.badge.icon_image_id}}{%endif%}" width="100%" style="aspect-ratio: 1/1;" alt="{% if CategoryIndex != 11 %}{{item.asset.name}}{%else%}{{item.badge.name}}{%endif%}">
|
||||
{% if CategoryIndex != 11 %}
|
||||
{% if item.asset.is_limited: %}<p class="position-absolute m-0 fw-bold text-limited" style="bottom:-1px;left:0px;">LIMITED {%if item.asset.is_limited_unique: %}<span class="text-limitedu">U</span>{%else%}<span class="me-2"></span>{%endif%}</p>{% endif %}
|
||||
{% if item.asset.offsale_at != None %}<p class="position-absolute bg-danger m-0" style="top: 0px;right: 0px;border-bottom-left-radius: 10px;padding: 5px;"><i class="bi bi-alarm text-white"></i></p>{% endif %}
|
||||
{% if item.serial != None %}<p class="position-absolute m-0 rounded p-1 fw-bold bg-black border text-white" style="top: 5px; left: 5px;aspect-ratio: 1/1;font-size: 12px;">#{{item.serial}}</p>{%endif%}
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="text-secondary m-0" style="font-size:12px;">{% if CategoryIndex != 11 %}{{item.asset.name}}{%else%}{{item.badge.name}}{%endif%}</p>
|
||||
{% if CategoryIndex != 11 %}
|
||||
{% if item.asset.is_for_sale: %}
|
||||
{% if item.asset.price_robux == 0 and item.asset.price_tix == 0: %}
|
||||
<p class="text-robux m-0 ">FREE</p>
|
||||
{% else %}
|
||||
{% if item.asset.price_robux != 0: %}
|
||||
<p class="text-robux m-0">R$ {{item.asset.price_robux}}</p>
|
||||
{% endif %}
|
||||
{% if item.asset.price_tix != 0: %}
|
||||
<p class="text-tickets m-0">T$ {{item.asset.price_tix}}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if item.asset.is_limited_unique : %}
|
||||
<p class="text-remaining m-0">Remaining: <span class="text-white">{{item.asset.serial_count - item.asset.sale_count}}</span></p>
|
||||
{%endif%}
|
||||
{% else %}
|
||||
{% if item.asset.is_limited: %}
|
||||
{% if item.asset.price_robux == 0 and item.asset.price_tix != 0: %}
|
||||
<p class="text-tickets m-0"><span class="text-secondary fw-normal">was</span> T$ {{item.asset.price_tix or "-"}}</p>
|
||||
{% elif item.asset.price_robux != 0 and item.asset.price_tix != 0: %}
|
||||
<p class="text-robux m-0"><span class="text-secondary fw-normal">was</span> R$ {{item.asset.price_robux or "-"}}</p>
|
||||
{% elif item.asset.price_robux != 0 and item.asset.price_tix == 0: %}
|
||||
<p class="text-robux m-0"><span class="text-secondary fw-normal">was</span> R$ {{item.asset.price_robux or "-"}}</p>
|
||||
{% else %}
|
||||
<p class="text-robux m-0"><span class="text-secondary fw-normal">was</span> FREE</p>
|
||||
{% endif %}
|
||||
<p class="text-robux m-0"><span class="text-secondary fw-normal">now</span> R$ {{item.best_price}}</p>
|
||||
{%else%}
|
||||
<p class="text-secondary m-0 ">Offsale</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div></a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if len(SearchQuery.items) == 0: %}
|
||||
<p class="w-100 text-center mt-5 mb-5">No items found</p>
|
||||
{% endif %}
|
||||
<div class="align-items-center d-flex justify-content-center mt-2 mb-2">
|
||||
<a class="ms-auto m-0 text-decoration-none {% if not SearchQuery.has_prev %}text-secondary{%endif%}" {% if SearchQuery.has_prev %}href="/users/{{profile.id}}/inventory?page={{SearchQuery.prev_num}}&category={{CategoryIndex}}"{%endif%}>Previous</a>
|
||||
<p class="ms-2 me-2 text-white m-0">Page {{SearchQuery.page}} of {{SearchQuery.pages}}</p>
|
||||
<a class="me-auto m-0 text-decoration-none {% if not SearchQuery.has_next %}text-secondary{%endif%}" {% if SearchQuery.has_next %}href="/users/{{profile.id}}/inventory?page={{SearchQuery.next_num}}&category={{CategoryIndex}}"{%endif%}>Next</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,254 @@
|
||||
{% extends '__layout__.html' %}
|
||||
{% block title %}{{profile.username}}{% endblock %}
|
||||
{% block head %}
|
||||
<link href="/static/css/profile.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
|
||||
<style>
|
||||
.text-secondary {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.border {
|
||||
border-color: rgb(60,60,60) !important;
|
||||
}
|
||||
.nav-link {
|
||||
color: #c7c7c7!important
|
||||
}
|
||||
.nav-link.active {
|
||||
color: #fff!important
|
||||
}
|
||||
.place-card {
|
||||
height: 220px !important;
|
||||
aspect-ratio: 0.640909/1;
|
||||
}
|
||||
.place-card-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
gap: 0px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="main">
|
||||
<div class="container" style="max-width: 800px;">
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div>
|
||||
{% for message in messages %}
|
||||
<div class="alert border border-danger p-2 text-center messagealerts">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-7 d-flex">
|
||||
<div class="image-container">
|
||||
<img class="rounded overflow-hidden" src="/Thumbs/Head.ashx?x=150&y=150&userId={{profile.id}}" width="130px" height="130px" alt="{{profile.username}}"/>
|
||||
{% if profile.isonline: %}<div class="status-icon {% if profile.ingame: %}status-icon-green{% endif %}"></div>{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex align-items-center">
|
||||
<h1 class="m-0">{{profile.username}}</h1>
|
||||
{% if profile.membershipValue == 1: %}
|
||||
<span class="rbx-icon-bc ms-2"></span>
|
||||
{%endif%}
|
||||
{% if profile.membershipValue == 2: %}
|
||||
<span class="rbx-icon-tbc ms-2"></span>
|
||||
{%endif%}
|
||||
{% if profile.membershipValue == 3: %}
|
||||
<span class="rbx-icon-negative-obc ms-2"></span>
|
||||
{%endif%}
|
||||
</div>
|
||||
{% if profile.id != currentuser.id : %}
|
||||
{% if profile.doestargetuserfollowviewer: %}
|
||||
<p class="m-0" style="font-size: 11px;">This user is currently following you</p>
|
||||
{%endif%}
|
||||
{%endif%}
|
||||
<div class="d-flex mt-1">
|
||||
<div style="width: fit-content;" class="me-3">
|
||||
<p class="m-0 text-secondary w-100 text-center d-block" style="font-size: 15px;">Friends</p>
|
||||
<a href="/users/{{profile.id}}/friends" class="m-0 text-decoration-none text-white w-100 text-center d-block" style="font-size: 25px;">{{profile.friendcount}}</a>
|
||||
</div>
|
||||
<div style="width: fit-content;" class="me-3">
|
||||
<p class="m-0 text-secondary w-100 text-center d-block" style="font-size: 15px;">Followers</p>
|
||||
<a href="/users/{{profile.id}}/followers" class="m-0 text-decoration-none text-white w-100 text-center d-block" style="font-size: 25px;">{{profile.FollowerCount}}</a>
|
||||
</div>
|
||||
<div style="width: fit-content;" class="me-3">
|
||||
<p class="m-0 text-secondary w-100 text-center d-block" style="font-size: 15px;">Following</p>
|
||||
<a href="/users/{{profile.id}}/following" class="m-0 text-decoration-none text-white w-100 text-center d-block" style="font-size: 25px;">{{profile.FollowingCount}}</a>
|
||||
</div>
|
||||
<div style="width: fit-content;">
|
||||
<p class="m-0 text-secondary w-100 text-center d-block" style="font-size: 15px;">User RAP</p>
|
||||
<p class="m-0 text-decoration-none text-white w-100 text-center d-block" style="font-size: 25px;">{{profile.UserRAP}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div>
|
||||
<div class="dropdown d-flex justify-content-end">
|
||||
<button class="btn" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
{% if profile.id != currentuser.id : %}
|
||||
{% if not profile.isviewerfollowing: %}
|
||||
<li><form action="/users/{{profile.id}}/followuser" method="post"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/><button class="dropdown-item" type="submit">Follow</button></form></li>
|
||||
{%else%}
|
||||
<li><form action="/users/{{profile.id}}/unfollowuser" method="post"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/><button class="dropdown-item" type="submit">Unfollow</button></form></li>
|
||||
{%endif%}
|
||||
<li><a class="dropdown-item" href="/trade/{{profile.id}}/create">Trade</a></li>
|
||||
{% endif %}
|
||||
<li><a class="dropdown-item" href="/users/{{profile.id}}/inventory">Inventory</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% if profile.id != currentuser.id : %}
|
||||
<div class="d-flex justify-content-end me-3 mt-4">
|
||||
<a href="/messages/new/{{profile.id}}"><button class="btn border me-2 text-white">Message</button></a>
|
||||
{% if profile.isfriends: %}
|
||||
<form action="/users/{{profile.id}}/unfriend" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button class="btn border-danger text-danger">Unfriend</button>
|
||||
</form>
|
||||
{% else: %}
|
||||
{% if profile.friendrequestpending: %}
|
||||
<button class="btn border" disabled>Pending</button>
|
||||
{% else: %}
|
||||
{% if profile.friendrequesttoauthenticateduser: %}
|
||||
<form action="/users/{{profile.id}}/acceptfriend" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button class="btn border-success">Accept Friend Request</button>
|
||||
</form>
|
||||
{% else: %}
|
||||
<form action="/users/{{profile.id}}/addfriend" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button class="btn border text-white">Add Friend</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="mt-3 rounded-top mb-2" style="background-color: rgb(40, 40, 40) !important;overflow: hidden;">
|
||||
<div class="nav nav-underline nav-fill page-tabs" id="nav-tab" role="tablist">
|
||||
<a href="#about" class="nav-link active" id="nav-about-tab" data-bs-toggle="tab" data-bs-target="#nav-about" type="button" role="tab" aria-controls="nav-about" aria-selected="true">About</a>
|
||||
<a href="#games" class="nav-link" id="nav-games-tab" data-bs-toggle="tab" data-bs-target="#nav-games" type="button" role="tab" aria-controls="nav-games" aria-selected="false">Games</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tab-content ps-2 pe-2 pt-1 pb-3" id="nav-tabContent">
|
||||
<div class="tab-pane show active" id="nav-about" role="tabpanel" aria-labelledby="nav-about-tab" tabindex="0">
|
||||
<h5 class="m-0 mb-1">Description</h5>
|
||||
{% for line in descriptionlines: %}
|
||||
{% if line.strip() != '' %}
|
||||
<p class="text-secondary m-0">{{line}}</p>
|
||||
{%else%}
|
||||
<br>
|
||||
{%endif%}
|
||||
{% endfor %}
|
||||
<div class="linebreak"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 p-2">
|
||||
<img src="/Thumbs/Avatar.ashx?x=420&y=420&userId={{profile.id}}" class="w-100 border rounded" alt="{{profile.username}} Avatar" />
|
||||
</div>
|
||||
<div class="col-md-6 p-2">
|
||||
<div class="border rounded w-100 h-100 p-2">
|
||||
<h5 class="text-center w-100 m-0">Currently wearing</h5>
|
||||
<div class="row p-1">
|
||||
{% for asset in profile.wearing %}
|
||||
<div class="col-3 p-1">
|
||||
<a href="/catalog/{{asset.asset_id}}/">
|
||||
<img src="/Thumbs/Asset.ashx?assetId={{asset.asset_id}}&x=100&y=100" width="100%" class="rounded p-1" style="background-color: rgb(30,30,30);">
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<h5>Friends</h5>
|
||||
<div class="ms-auto">
|
||||
<a href="/users/{{profile.id}}/friends" class="text-decoration-none">View All ( {{profile.friendcount}} )</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded dark-container p-2 d-flex">
|
||||
{% for friend in profile.friendsdata %}
|
||||
<a href="/users/{{friend.id}}/profile">
|
||||
<div style="height: fit-content; width: 90px;" class="me-3">
|
||||
<div class="image-container-friend">
|
||||
<img class="rounded w-100 d-table" src="/Thumbs/Head.ashx?x=100&y=100&userId={{friend.id}}" style="margin: 0 auto; max-width: 100px;aspect-ratio: 1/1;" alt="{{friend.username}}"/>
|
||||
{% if friend.isonline: %}<div class="status-icon {% if friend.ingame: %}status-icon-green{% endif %}"></div>{% endif %}
|
||||
</div>
|
||||
<p class="text-white w-100 text-center d-inline-block m-0 text-truncate" style="font-size: 12px;">{{friend.username}}</p>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% if profile.friendcount == 0: %}
|
||||
<p style="margin: auto auto;" class="d-table">This user has no friends{% if profile.id != currentuser.id : %}, maybe you should send them a friend request!{% endif %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="rounded mt-3 mb-3 p-2 dark-container row d-flex align-items-center" style="min-height: 75px !important;">
|
||||
<div class="col text-center">
|
||||
<b class="text-secondary">Join Date</b>
|
||||
<h4>{{profile.joindate}}</h4>
|
||||
</div>
|
||||
<div class="col text-center">
|
||||
<b class="text-secondary">Place Visits</b>
|
||||
<h4>{{profile.TotalVisits}}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="nav-games" role="tabpanel" aria-labelledby="nav-games-tab" tabindex="0">
|
||||
<h5 class="m-0 mb-1">Games</h5>
|
||||
<div class="w-100 place-card-container">
|
||||
{% for place in profile.UserGames: %}
|
||||
<a class="text-decoration-none p-1" href="/games/{{place.id}}/{{place.slug}}">
|
||||
<div class="overflow-hidden place-card w-100">
|
||||
<div class="position-relative">
|
||||
<img width="100%" style="aspect-ratio: 1/1;" src="/Thumbs/PlaceIcon.ashx?assetId={{place.id}}&x=150&y=150" alt="{{place.name}}">
|
||||
<div class="position-absolute" style="bottom: 0px;left: 0px;">
|
||||
<div class="fw-bold bg-dark text-white" style="font-size: 12px;padding: 3px;border-top-right-radius: 4px;">{{place.placeyear.value}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-1">
|
||||
<h5 class="mb-0" style="font-size: 17px;text-overflow: ellipsis;">{{place.name}}</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<p style="margin:0;font-size: 14px;" class="text-secondary"><i class="bi bi-people"></i> {{place.playingcount}} <span style="font-size: 13px;">Playing</span></p>
|
||||
</div>
|
||||
<div class="w-100 votePercentageBackground">
|
||||
<div class="votePercentageFill" style="width: {{place.likePercentage}}%;"></div>
|
||||
<div>
|
||||
<div class="segment" style="left: 18%;"></div>
|
||||
<div class="segment" style="left: 38%;"></div>
|
||||
<div class="segment" style="left: 58%;"></div>
|
||||
<div class="segment" style="left: 78%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if len(profile.UserGames) == 0:%}
|
||||
<p class="text-center w-100 p-5">This user has no games</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", async function() {
|
||||
var Navtabs = document.querySelectorAll(".page-tabs a");
|
||||
Navtabs.forEach(function(element) {
|
||||
element.addEventListener("click", function() {
|
||||
var TabTarget = element.getAttribute("href");
|
||||
window.location.hash = TabTarget;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,536 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, jsonify, make_response, flash, abort
|
||||
from app.util import auth, friends, membership, websiteFeatures, placeinfo
|
||||
from app.extensions import db, limiter, csrf, user_limiter
|
||||
from app.models.user import User
|
||||
from app.models.friend_request import FriendRequest
|
||||
from app.models.friend_relationship import FriendRelationship
|
||||
from datetime import datetime
|
||||
from app.models.asset import Asset
|
||||
from app.models.place import Place
|
||||
from app.models.placeserver_players import PlaceServerPlayer
|
||||
from app.models.follow_relationship import FollowRelationship
|
||||
from app.models.user_avatar_asset import UserAvatarAsset
|
||||
from app.models.userassets import UserAsset
|
||||
from app.models.universe import Universe
|
||||
from app.models.place_badge import PlaceBadge, UserBadge
|
||||
from app.enums.AssetType import AssetType
|
||||
from sqlalchemy import or_
|
||||
from app.enums.MembershipType import MembershipType
|
||||
from slugify import slugify
|
||||
from app.routes.rate import GetAssetLikesAndDislikes, GetUserVoteStatus, GetAssetVotePercentage, GetAssetFavoriteCount, GetUserFavoriteStatus
|
||||
from app.services import economy
|
||||
|
||||
from app.services.user_relationships import followings
|
||||
|
||||
Profile = Blueprint("profile", __name__, template_folder="pages")
|
||||
|
||||
@Profile.route("/users/<int:userid>/profile", methods=["GET"])
|
||||
@auth.authenticated_required
|
||||
def profile_page(userid):
|
||||
user : User = User.query.filter_by(id=userid).first()
|
||||
if user is None:
|
||||
abort(404)
|
||||
if user.accountstatus == 4 or user.accountstatus == 3:
|
||||
abort(404)
|
||||
|
||||
Friends = friends.GetFriends(user.id)
|
||||
FriendCount = len(Friends)
|
||||
DescriptionLines = user.description.split("\n")
|
||||
FriendsData = []
|
||||
for friend in Friends:
|
||||
friendObjData = {}
|
||||
friendObjData = {
|
||||
"id": friend.id,
|
||||
"username": friend.username,
|
||||
"isonline": True if (datetime.utcnow() - friend.lastonline).total_seconds() < 60 else False,
|
||||
"ingame": True if PlaceServerPlayer.query.filter_by(userid=friend.id).first() is not None else False
|
||||
}
|
||||
FriendsData.append(friendObjData)
|
||||
FriendsData.sort(key=lambda x: x["isonline"], reverse=True)
|
||||
FriendsData.sort(key=lambda x: x["ingame"], reverse=True)
|
||||
FriendsData = FriendsData[:8]
|
||||
|
||||
AuthenticatedUser = auth.GetCurrentUser()
|
||||
if AuthenticatedUser.id != user.id:
|
||||
isFriends = friends.IsFriends(AuthenticatedUser.id, user.id)
|
||||
FriendRequestPending = FriendRequest.query.filter_by(requester_id=AuthenticatedUser.id, requestee_id=user.id).first() is not None
|
||||
FriendRequestToAuthenticatedUser = FriendRequest.query.filter_by(requester_id=user.id, requestee_id=AuthenticatedUser.id).first() is not None
|
||||
isViewerFollowing = FollowRelationship.query.filter_by(followerUserId=AuthenticatedUser.id, followeeUserId=user.id).first() is not None
|
||||
doesTargetUserFollowViewer = FollowRelationship.query.filter_by(followerUserId=user.id, followeeUserId=AuthenticatedUser.id).first() is not None
|
||||
else:
|
||||
isFriends = False
|
||||
FriendRequestPending = False
|
||||
FriendRequestToAuthenticatedUser = False
|
||||
isViewerFollowing = False
|
||||
doesTargetUserFollowViewer = False
|
||||
joindate = user.created.strftime("%d/%m/%Y")
|
||||
TotalVisits = 0
|
||||
UniverseList : list[Universe] = Universe.query.filter_by( creator_id = user.id, creator_type = 0 ).all()
|
||||
for UniverseObj in UniverseList:
|
||||
TotalVisits += UniverseObj.visit_count
|
||||
|
||||
FollowerCount = followings.get_follower_count( user )
|
||||
FollowingCount = followings.get_following_count( user )
|
||||
WearingAssets = UserAvatarAsset.query.filter_by(user_id=user.id).all()
|
||||
membershipValue : int = membership.GetUserMembership(user.id).value
|
||||
|
||||
UserGames : list[Asset] = Asset.query.filter_by(creator_id = user.id, creator_type = 0, asset_type = AssetType.Place).all()
|
||||
UserGamesInfo : list[dict] = []
|
||||
for UserGame in UserGames:
|
||||
PlaceObj : Place = Place.query.filter_by(placeid=UserGame.id).first()
|
||||
if PlaceObj is None:
|
||||
continue
|
||||
if PlaceObj.is_public == False:
|
||||
continue
|
||||
|
||||
PlaceObjData = {
|
||||
"id": UserGame.id,
|
||||
"name": UserGame.name,
|
||||
"playingcount": placeinfo.GetPlayingCount( PlaceObj ),
|
||||
"slug": slugify(UserGame.name, lowercase=False) if UserGame.name is not None else "",
|
||||
"likePercentage": GetAssetVotePercentage(UserGame.id),
|
||||
"placeyear": PlaceObj.placeyear
|
||||
}
|
||||
UserGamesInfo.append(PlaceObjData)
|
||||
|
||||
ProfileData = {
|
||||
"username": user.username,
|
||||
"id": user.id,
|
||||
"friendcount": FriendCount,
|
||||
"isonline": True if (datetime.utcnow() - user.lastonline).total_seconds() < 60 else False,
|
||||
"ingame": True if PlaceServerPlayer.query.filter_by(userid=user.id).first() is not None else False,
|
||||
"isfriends": isFriends,
|
||||
"friendrequestpending": FriendRequestPending,
|
||||
"friendrequesttoauthenticateduser": FriendRequestToAuthenticatedUser,
|
||||
"friendsdata": FriendsData,
|
||||
"joindate": joindate,
|
||||
"TotalVisits": TotalVisits,
|
||||
"FollowerCount": FollowerCount,
|
||||
"FollowingCount": FollowingCount,
|
||||
"wearing": WearingAssets,
|
||||
"isviewerfollowing": isViewerFollowing,
|
||||
"doestargetuserfollowviewer": doesTargetUserFollowViewer,
|
||||
"membershipValue": membershipValue,
|
||||
"UserGames": UserGamesInfo,
|
||||
"UserRAP": economy.CalculateUserRAP( user )
|
||||
}
|
||||
|
||||
return render_template("profiles/profile.html", profile=ProfileData, descriptionlines=DescriptionLines)
|
||||
|
||||
@Profile.route("/users/<int:userid>/addfriend", methods=["POST"])
|
||||
@limiter.limit("5/minute")
|
||||
@auth.authenticated_required
|
||||
@user_limiter.limit("5/minute")
|
||||
def add_friend(userid):
|
||||
user = User.query.filter_by(id=userid).first()
|
||||
if user is None:
|
||||
abort(404)
|
||||
if user.accountstatus == 4 or user.accountstatus == 3:
|
||||
abort(404)
|
||||
if not websiteFeatures.GetWebsiteFeature("SendFriendRequest"):
|
||||
flash("Adding friends is temporarily disabled.", "error")
|
||||
return redirect(f"/users/{user.id}/profile")
|
||||
AuthenticatedUser = auth.GetCurrentUser()
|
||||
|
||||
if userid == AuthenticatedUser.id:
|
||||
flash("You cannot add yourself as a friend.", "error")
|
||||
return redirect(f"/users/{user.id}/profile")
|
||||
|
||||
if FriendRequest.query.filter_by(requester_id=AuthenticatedUser.id, requestee_id=user.id).first() is not None:
|
||||
flash("You have already sent a friend request to this user.", "error")
|
||||
return redirect(f"/users/{user.id}/profile")
|
||||
|
||||
if friends.IsFriends(AuthenticatedUser.id, user.id):
|
||||
flash("You are already friends with this user.", "error")
|
||||
return redirect(f"/users/{user.id}/profile")
|
||||
|
||||
friendRequest = FriendRequest(requester_id=AuthenticatedUser.id, requestee_id=user.id)
|
||||
db.session.add(friendRequest)
|
||||
db.session.commit()
|
||||
|
||||
return redirect(f"/users/{user.id}/profile")
|
||||
|
||||
@Profile.route("/users/<int:userid>/unfriend", methods=["POST"])
|
||||
@limiter.limit("5/minute")
|
||||
@auth.authenticated_required
|
||||
@user_limiter.limit("5/minute")
|
||||
def unfriend(userid):
|
||||
user = User.query.filter_by(id=userid).first()
|
||||
if user is None:
|
||||
abort(404)
|
||||
AuthenticatedUser = auth.GetCurrentUser()
|
||||
|
||||
if not friends.IsFriends(AuthenticatedUser.id, user.id):
|
||||
flash("You are not friends with this user.", "error")
|
||||
return handle_redirect(user)
|
||||
|
||||
Friendrelationship : FriendRelationship = friends.GetFriendRelationship(AuthenticatedUser.id, user.id)
|
||||
db.session.delete(Friendrelationship)
|
||||
db.session.commit()
|
||||
|
||||
return handle_redirect(user)
|
||||
|
||||
def handle_redirect(user):
|
||||
wantRedirect = request.args.get("redirect", type=int, default=1)
|
||||
if wantRedirect == 1:
|
||||
return redirect(f"/users/{user.id}/profile")
|
||||
else:
|
||||
return redirect(request.referrer)
|
||||
|
||||
|
||||
@Profile.route("/users/<int:userid>/acceptfriend", methods=["POST"])
|
||||
@limiter.limit("30/minute")
|
||||
@auth.authenticated_required
|
||||
@user_limiter.limit("30/minute")
|
||||
def accept_friend(userid):
|
||||
user = User.query.filter_by(id=userid).first()
|
||||
if user is None:
|
||||
abort(404)
|
||||
if user.accountstatus == 4 or user.accountstatus == 3:
|
||||
abort(404)
|
||||
if not websiteFeatures.GetWebsiteFeature("SendFriendRequest"):
|
||||
flash("Adding friends is temporarily disabled.", "error")
|
||||
return handle_redirect(user)
|
||||
AuthenticatedUser = auth.GetCurrentUser()
|
||||
if userid == AuthenticatedUser.id:
|
||||
flash("You cannot add yourself as a friend.", "error")
|
||||
return handle_redirect(user)
|
||||
|
||||
friendRequest = FriendRequest.query.filter_by(requester_id=user.id, requestee_id=AuthenticatedUser.id).first()
|
||||
if friendRequest is None:
|
||||
flash("Friend request not found.", "error")
|
||||
return handle_redirect(user)
|
||||
|
||||
FriendCount = len(friends.GetFriends(user.id))
|
||||
if FriendCount >= 200:
|
||||
flash("This user has reached their friend limit.", "error")
|
||||
return handle_redirect(user)
|
||||
|
||||
AuthenticatedUserFriendCount = len(friends.GetFriends(AuthenticatedUser.id))
|
||||
if AuthenticatedUserFriendCount >= 200:
|
||||
flash("You have reached your friend limit.", "error")
|
||||
return handle_redirect(user)
|
||||
|
||||
Friendrelationship = FriendRelationship(user_id=user.id, friend_id=AuthenticatedUser.id)
|
||||
db.session.add(Friendrelationship)
|
||||
db.session.delete(friendRequest)
|
||||
db.session.commit()
|
||||
|
||||
return handle_redirect(user)
|
||||
|
||||
@Profile.route("/users/<int:userid>/declinefriend", methods=["POST"])
|
||||
@limiter.limit("30/minute")
|
||||
@auth.authenticated_required
|
||||
@user_limiter.limit("30/minute")
|
||||
def decline_friend(userid):
|
||||
TargetUser = User.query.filter_by(id=userid).first()
|
||||
if TargetUser is None:
|
||||
abort(404)
|
||||
|
||||
if not websiteFeatures.GetWebsiteFeature("SendFriendRequest"):
|
||||
flash("Declining friend requests is temporarily disabled.", "error")
|
||||
return handle_redirect(TargetUser)
|
||||
AuthenticatedUser = auth.GetCurrentUser()
|
||||
if userid == AuthenticatedUser.id:
|
||||
abort(404)
|
||||
friendRequest = FriendRequest.query.filter_by(requester_id=TargetUser.id, requestee_id=AuthenticatedUser.id).first()
|
||||
if friendRequest is None:
|
||||
return handle_redirect(TargetUser)
|
||||
db.session.delete(friendRequest)
|
||||
db.session.commit()
|
||||
return handle_redirect(TargetUser)
|
||||
|
||||
|
||||
@Profile.route("/users/<int:userid>/unfollowuser", methods=["POST"])
|
||||
@limiter.limit("5/minute")
|
||||
@auth.authenticated_required
|
||||
def unfollow_user(userid):
|
||||
UserObj : User = User.query.filter_by(id=userid).first()
|
||||
if UserObj is None:
|
||||
abort(404)
|
||||
|
||||
AuthenticatedUser : User = auth.GetCurrentUser()
|
||||
try:
|
||||
followings.unfollow_user(
|
||||
current_follower = AuthenticatedUser, followed_user = UserObj
|
||||
)
|
||||
except followings.FollowingExceptions.UserNotFollowing:
|
||||
flash("You are not following this user.", "error")
|
||||
return redirect(f"/users/{UserObj.id}/profile")
|
||||
except followings.FollowingExceptions.UserRateLimited:
|
||||
flash("You are being rate limited from unfollowing users, please slow down.", "error")
|
||||
return redirect(f"/users/{UserObj.id}/profile")
|
||||
|
||||
return redirect(f"/users/{UserObj.id}/profile")
|
||||
|
||||
@Profile.route("/users/<int:userid>/followuser", methods=["POST"])
|
||||
@limiter.limit("5/minute")
|
||||
@auth.authenticated_required
|
||||
def follow_user(userid):
|
||||
UserObj : User = User.query.filter_by( id = userid ).first()
|
||||
if UserObj is None:
|
||||
abort(404)
|
||||
if UserObj.accountstatus == 4 or UserObj.accountstatus == 3:
|
||||
abort(404)
|
||||
|
||||
AuthenticatedUser : User = auth.GetCurrentUser()
|
||||
try:
|
||||
followings.follow_user(
|
||||
follower_user = AuthenticatedUser, followed_user = UserObj
|
||||
)
|
||||
except followings.FollowingExceptions.AlreadyFollowing:
|
||||
flash("You are already following this user.", "error")
|
||||
return redirect(f"/users/{UserObj.id}/profile")
|
||||
except followings.FollowingExceptions.UserRateLimited:
|
||||
flash("You are being rate limited from following users, please slow down.", "error")
|
||||
return redirect(f"/users/{UserObj.id}/profile")
|
||||
except followings.FollowingExceptions.CannotFollowSelf:
|
||||
flash("You cannot follow yourself.", "error")
|
||||
return redirect(f"/users/{UserObj.id}/profile")
|
||||
except followings.FollowingExceptions.FollowingIsDisabled:
|
||||
flash("Following users is disabled.", "error")
|
||||
return redirect(f"/users/{UserObj.id}/profile")
|
||||
|
||||
return redirect(f"/users/{UserObj.id}/profile")
|
||||
|
||||
@Profile.route("/users/<int:userid>/friends", methods=["GET"])
|
||||
@auth.authenticated_required
|
||||
def FriendsPage( userid : int ):
|
||||
user = User.query.filter_by(id=userid).first()
|
||||
if user is None:
|
||||
abort(404)
|
||||
if user.accountstatus == 4 or user.accountstatus == 3:
|
||||
abort(404)
|
||||
PageNumber = request.args.get("page", 1, type=int)
|
||||
|
||||
from app.models.friend_relationship import FriendRelationship
|
||||
FriendRelationships : list[FriendRelationship] = FriendRelationship.query.filter(
|
||||
or_(
|
||||
FriendRelationship.user_id == user.id,
|
||||
FriendRelationship.friend_id == user.id
|
||||
)
|
||||
).join(
|
||||
User,
|
||||
or_(
|
||||
FriendRelationship.user_id == User.id,
|
||||
FriendRelationship.friend_id == User.id
|
||||
)
|
||||
).filter(
|
||||
User.id != user.id
|
||||
).order_by(
|
||||
User.username
|
||||
).paginate(
|
||||
page=PageNumber,
|
||||
per_page=18,
|
||||
error_out=False
|
||||
)
|
||||
FriendUsers : list[User] = []
|
||||
for FriendRelationshipObj in FriendRelationships:
|
||||
if FriendRelationshipObj.user_id == user.id:
|
||||
TargetUserId : int = FriendRelationshipObj.friend_id
|
||||
else:
|
||||
TargetUserId : int = FriendRelationshipObj.user_id
|
||||
FriendUser : User = User.query.filter_by(id=TargetUserId).first()
|
||||
if FriendUser is not None:
|
||||
FriendUsers.append({
|
||||
"id": FriendUser.id,
|
||||
"username": FriendUser.username,
|
||||
"isonline": True if (datetime.utcnow() - FriendUser.lastonline).total_seconds() < 60 else False,
|
||||
"ingame": True if PlaceServerPlayer.query.filter_by(userid=FriendUser.id).first() is not None else False
|
||||
})
|
||||
FriendCount = FriendRelationship.query.filter((FriendRelationship.user_id == user.id) | (FriendRelationship.friend_id == user.id)).count()
|
||||
if FriendRelationships.pages < PageNumber:
|
||||
redirect(f"/users/{user.id}/friends")
|
||||
|
||||
return render_template("profiles/friends.html",
|
||||
profile=user,
|
||||
FriendUsers=FriendUsers,
|
||||
PageNumber=PageNumber,
|
||||
isThereNextPage=FriendRelationships.has_next,
|
||||
isTherePreviousPage=FriendRelationships.has_prev,
|
||||
FriendCount=FriendCount,
|
||||
TotalPages=FriendRelationships.pages)
|
||||
|
||||
@Profile.route("/users/<int:userid>/following", methods=["GET"])
|
||||
@auth.authenticated_required
|
||||
def FollowingPage( userid : int ):
|
||||
user = User.query.filter_by(id=userid).first()
|
||||
if user is None:
|
||||
abort(404)
|
||||
if user.accountstatus == 4 or user.accountstatus == 3:
|
||||
abort(404)
|
||||
PageNumber = request.args.get("page", 1, type=int)
|
||||
|
||||
from app.models.follow_relationship import FollowRelationship
|
||||
FollowRelationships : list[FollowRelationship] = FollowRelationship.query.filter_by(followerUserId=user.id).join(
|
||||
User,
|
||||
FollowRelationship.followeeUserId == User.id
|
||||
).order_by(
|
||||
User.username
|
||||
).paginate(
|
||||
page=PageNumber,
|
||||
per_page=18,
|
||||
error_out=False
|
||||
)
|
||||
FollowUsers : list[User] = []
|
||||
for FollowRelationship in FollowRelationships:
|
||||
FollowUser : User = User.query.filter_by(id=FollowRelationship.followeeUserId).first()
|
||||
if FollowUser is not None:
|
||||
FollowUsers.append({
|
||||
"id": FollowUser.id,
|
||||
"username": FollowUser.username,
|
||||
"isonline": True if (datetime.utcnow() - FollowUser.lastonline).total_seconds() < 60 else False,
|
||||
"ingame": True if PlaceServerPlayer.query.filter_by(userid=FollowUser.id).first() is not None else False
|
||||
})
|
||||
FollowCount = FollowRelationship.query.filter_by(followerUserId=user.id).count()
|
||||
if FollowRelationships.pages < PageNumber:
|
||||
redirect(f"/users/{user.id}/following")
|
||||
|
||||
return render_template("profiles/following.html",
|
||||
profile=user,
|
||||
FollowUsers=FollowUsers,
|
||||
PageNumber=PageNumber,
|
||||
isThereNextPage=FollowRelationships.has_next,
|
||||
isTherePreviousPage=FollowRelationships.has_prev,
|
||||
FollowCount=FollowCount,
|
||||
TotalPages=FollowRelationships.pages)
|
||||
|
||||
@Profile.route("/users/<int:userid>/followers", methods=["GET"])
|
||||
@auth.authenticated_required
|
||||
def FollowersPage( userid : int ):
|
||||
user = User.query.filter_by(id=userid).first()
|
||||
if user is None:
|
||||
abort(404)
|
||||
if user.accountstatus == 4 or user.accountstatus == 3:
|
||||
abort(404)
|
||||
PageNumber = request.args.get("page", 1, type=int)
|
||||
|
||||
from app.models.follow_relationship import FollowRelationship
|
||||
FollowRelationships : list[FollowRelationship] = FollowRelationship.query.filter_by(followeeUserId=user.id).join(
|
||||
User,
|
||||
FollowRelationship.followerUserId == User.id
|
||||
).order_by(
|
||||
User.username
|
||||
).paginate(
|
||||
page=PageNumber,
|
||||
per_page=18,
|
||||
error_out=False
|
||||
)
|
||||
FollowUsers : list[User] = []
|
||||
for FollowRelationship in FollowRelationships:
|
||||
FollowUser : User = User.query.filter_by(id=FollowRelationship.followerUserId).first()
|
||||
if FollowUser is not None:
|
||||
FollowUsers.append({
|
||||
"id": FollowUser.id,
|
||||
"username": FollowUser.username,
|
||||
"isonline": True if (datetime.utcnow() - FollowUser.lastonline).total_seconds() < 60 else False,
|
||||
"ingame": True if PlaceServerPlayer.query.filter_by(userid=FollowUser.id).first() is not None else False
|
||||
})
|
||||
FollowCount = FollowRelationship.query.filter_by(followeeUserId=user.id).count()
|
||||
if FollowRelationships.pages < PageNumber:
|
||||
redirect(f"/users/{user.id}/followers")
|
||||
|
||||
return render_template("profiles/followers.html",
|
||||
profile=user,
|
||||
FollowUsers=FollowUsers,
|
||||
PageNumber=PageNumber,
|
||||
isThereNextPage=FollowRelationships.has_next,
|
||||
isTherePreviousPage=FollowRelationships.has_prev,
|
||||
FollowCount=FollowCount,
|
||||
TotalPages=FollowRelationships.pages)
|
||||
|
||||
@Profile.route("/users/<int:userid>/requests", methods=["GET"])
|
||||
@auth.authenticated_required
|
||||
def FriendRequestsPage( userid : int ):
|
||||
AuthenticatedUser = auth.GetCurrentUser()
|
||||
if AuthenticatedUser.id != userid:
|
||||
abort(404)
|
||||
PageNumber = request.args.get("page", 1, type=int)
|
||||
FriendRequests : list[FriendRequest] = FriendRequest.query.filter_by(requestee_id=userid).order_by(FriendRequest.created_at.desc()).paginate(page=PageNumber, per_page=18, error_out=False)
|
||||
return render_template("profiles/requests.html",
|
||||
profile=AuthenticatedUser,
|
||||
FriendRequests=FriendRequests)
|
||||
|
||||
@Profile.route("/users/<int:userid>/requests/clear", methods=["POST"])
|
||||
@auth.authenticated_required
|
||||
def ClearFriendRequests(userid : int):
|
||||
AuthenticatedUser = auth.GetCurrentUser()
|
||||
if AuthenticatedUser.id != userid:
|
||||
abort(404)
|
||||
FriendRequests : list[FriendRequest] = FriendRequest.query.filter_by(requestee_id=userid).all()
|
||||
for FriendRequestObj in FriendRequests:
|
||||
db.session.delete(FriendRequestObj)
|
||||
db.session.commit()
|
||||
return redirect(f"/users/{userid}/requests")
|
||||
|
||||
@Profile.route("/users/<int:userid>/inventory", methods=["GET"])
|
||||
@auth.authenticated_required
|
||||
def UserInventoryPage( userid : int ):
|
||||
UserObj : User = User.query.filter_by( id = userid ).first()
|
||||
if UserObj is None:
|
||||
return abort(404)
|
||||
|
||||
InventoryTypesDict = {
|
||||
0: lambda queryObj: queryObj.filter(Asset.asset_type.in_((
|
||||
AssetType.HairAccessory,
|
||||
AssetType.FaceAccessory,
|
||||
AssetType.NeckAccessory,
|
||||
AssetType.ShoulderAccessory,
|
||||
AssetType.FrontAccessory,
|
||||
AssetType.BackAccessory,
|
||||
AssetType.WaistAccessory,
|
||||
))),
|
||||
1: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Audio ),
|
||||
2: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Package ),
|
||||
3: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Face ),
|
||||
4: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Head ),
|
||||
5: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Hat ),
|
||||
6: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.GamePass ),
|
||||
7: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.TShirt ),
|
||||
8: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Shirt ),
|
||||
9: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Pants ),
|
||||
10: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Gear )
|
||||
}
|
||||
InventoryTypeToFriendlyName = {
|
||||
0: "Accessories",
|
||||
1: "Audio",
|
||||
2: "Packages",
|
||||
3: "Faces",
|
||||
4: "Heads",
|
||||
5: "Hats",
|
||||
6: "Passes",
|
||||
7: "T-Shirts",
|
||||
8: "Shirts",
|
||||
9: "Pants",
|
||||
10: "Gears",
|
||||
11: "Badges"
|
||||
}
|
||||
|
||||
CategoryType = request.args.get( key = "category", default = 0, type = int )
|
||||
if CategoryType not in InventoryTypeToFriendlyName:
|
||||
return redirect(f"/users/{userid}/inventory")
|
||||
PageNumber = request.args.get( key = "page", default = 1, type = int )
|
||||
if PageNumber < 1:
|
||||
PageNumber = 1
|
||||
|
||||
if CategoryType not in [11]:
|
||||
SearchQuery = UserAsset.query.filter_by( userid = userid ).outerjoin( Asset, Asset.id == UserAsset.assetid )
|
||||
SearchQuery = InventoryTypesDict[CategoryType](SearchQuery).order_by( UserAsset.updated.desc() ).paginate( page = PageNumber, per_page = 24, error_out = False )
|
||||
|
||||
for UserAssetObj in SearchQuery.items:
|
||||
if UserAssetObj.asset.is_limited and not UserAssetObj.asset.is_for_sale:
|
||||
BestPriceResult : UserAsset = UserAsset.query.filter_by(assetid=UserAssetObj.assetid, is_for_sale=True).order_by(UserAsset.price.asc()).first()
|
||||
if BestPriceResult is not None:
|
||||
UserAssetObj.best_price = str(BestPriceResult.price)
|
||||
else:
|
||||
UserAssetObj.best_price = "--"
|
||||
elif CategoryType == 11:
|
||||
SearchQuery = UserBadge.query.filter_by( user_id = userid ).order_by( UserBadge.awarded_at.desc() ).paginate( page = PageNumber, per_page = 24, error_out = False )
|
||||
|
||||
return render_template(
|
||||
"profiles/inventory.html",
|
||||
profile = UserObj,
|
||||
SearchQuery = SearchQuery,
|
||||
CategoryName = InventoryTypeToFriendlyName[CategoryType],
|
||||
CategoryIndex = CategoryType
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
{% extends '__layout__.html' %}
|
||||
{% block title %}Friend Requests{% endblock %}
|
||||
{% block head %}
|
||||
<link href="/static/css/profile.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
|
||||
<style>
|
||||
.text-secondary {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.border {
|
||||
border-color: rgb(60,60,60) !important;
|
||||
}
|
||||
.nav-link {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.nav-link.active {
|
||||
color: rgb(255, 255, 255) !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="main">
|
||||
<div class="container" style="max-width: 800px;">
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="/users/{{profile.id}}/profile">
|
||||
<img src="/Thumbs/Head.ashx?x=100&y=100&userId={{profile.id}}" width="80px" height="80px" class="rounded-2">
|
||||
</a>
|
||||
<h2 class="ms-3">Friend Requests</h2>
|
||||
</div>
|
||||
<nav class="mt-2 rounded-top" style="background-color: rgb(40, 40, 40) !important;overflow: hidden;">
|
||||
<div class="nav nav-underline nav-fill" id="nav-tab" role="tablist">
|
||||
<a class="nav-link" type="button" role="tab" aria-selected="false" href="/users/{{profile.id}}/friends">Friends</a>
|
||||
<a class="nav-link" type="button" role="tab" aria-selected="false" href="/users/{{profile.id}}/following">Following</a>
|
||||
<a class="nav-link" type="button" role="tab" aria-selected="false" href="/users/{{profile.id}}/followers">Followers</a>
|
||||
{% if profile.id == currentuser.id %}
|
||||
<a class="nav-link active" type="button" role="tab" aria-selected="true">Requests{% if currentuser.friend_requests > 0: %}<span class="badge text-bg-danger ms-1">{{currentuser.friend_requests}}</span>{%endif%}</a>
|
||||
{%endif%}
|
||||
</div>
|
||||
</nav>
|
||||
<div>
|
||||
<div class="d-flex align-items-center">
|
||||
<h4 class="m-0 mt-2">Requests ( {{FriendRequests.total}} )</h4>
|
||||
<form class="ms-auto mt-2" method="post" action="/users/{{profile.id}}/requests/clear">
|
||||
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
|
||||
<button class="btn btn-danger btn-sm" type="submit">Deny All</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
{% for request in FriendRequests: %}
|
||||
<div class="col-6 col-sm-4 mb-2">
|
||||
<div class="bg-dark border p-2 rounded d-flex">
|
||||
<a href="/users/{{request.requester.id}}/profile">
|
||||
<img src="/Thumbs/Head.ashx?x=100&y=100&userId={{request.requester.id}}" width="80px" height="80px" class="rounded-2" alt="{{request.requester.username}}">
|
||||
</a>
|
||||
<div class="h-100">
|
||||
<a class="text-white text-decoration-none ms-2" href="/users/{{request.requester.id}}/profile">{{request.requester.username}}</a>
|
||||
<div class="ms-2 mt-4 input-group flex-nowrap" >
|
||||
<form method="post" action="/users/{{request.requester.id}}/acceptfriend?redirect=0">
|
||||
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
|
||||
<button class="btn btn-success btn-sm rounded-end-0" type="submit"><span class="bi bi-check-lg"></span></button>
|
||||
</form>
|
||||
<form method="post" action="/users/{{request.requester.id}}/declinefriend?redirect=0">
|
||||
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
|
||||
<button class="btn btn-danger btn-sm rounded-start-0" type="submit"><span class="bi bi-x-lg"></span></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{%endfor%}
|
||||
</div>
|
||||
{% if FriendRequests.total <= 0: %}
|
||||
<p class="w-100 text-secondary text-center text-white mt-4 mb-4">No results found</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex mt-2 align-items-center">
|
||||
<a class="ms-auto text-decoration-none {%if not FriendRequests.has_prev:%}text-secondary{%endif%}" {%if FriendRequests.has_prev: %}href="/users/{{profile.id}}/requests?page={{FriendRequests.page - 1}}{%endif%}">Previous</a>
|
||||
<p class="m-0 ms-2 me-2 text-white">Page {{FriendRequests.page}} of {{FriendRequests.pages}}</p>
|
||||
<a class="me-auto text-decoration-none {%if not FriendRequests.has_next: %}text-secondary{%endif%}" {%if FriendRequests.has_next: %}href="/users/{{profile.id}}/requests?page={{FriendRequests.page + 1}}{%endif%}">Next</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user