leaderboard refactor (#526)
This commit is contained in:
parent
22ad4f5d23
commit
44919507e9
9 changed files with 436 additions and 600 deletions
252
files/classes/leaderboard.py
Normal file
252
files/classes/leaderboard.py
Normal file
|
@ -0,0 +1,252 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Final, Optional
|
||||
|
||||
from sqlalchemy import Column, func
|
||||
from sqlalchemy.orm import scoped_session, Query
|
||||
|
||||
from files.helpers.const import LEADERBOARD_LIMIT
|
||||
|
||||
from files.classes.badges import Badge
|
||||
from files.classes.marsey import Marsey
|
||||
from files.classes.user import User
|
||||
from files.classes.userblock import UserBlock
|
||||
from files.helpers.get import get_accounts_dict
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class LeaderboardMeta:
|
||||
header_name:str
|
||||
table_header_name:str
|
||||
html_id:str
|
||||
table_column_name:str
|
||||
user_relative_url:Optional[str]
|
||||
limit:int=LEADERBOARD_LIMIT
|
||||
|
||||
class Leaderboard:
|
||||
def __init__(self, v:Optional[User], meta:LeaderboardMeta) -> None:
|
||||
self.v:Optional[User] = v
|
||||
self.meta:LeaderboardMeta = meta
|
||||
|
||||
@property
|
||||
def all_users(self) -> list[User]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def v_position(self) -> Optional[int]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def v_value(self) -> Optional[int]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def v_appears_in_ranking(self) -> bool:
|
||||
return self.v_position is not None and self.v_position <= len(self.all_users)
|
||||
|
||||
@property
|
||||
def user_func(self) -> Callable[[Any], User]:
|
||||
return lambda u:u
|
||||
|
||||
@property
|
||||
def value_func(self) -> Callable[[User], int]:
|
||||
raise NotImplementedError()
|
||||
|
||||
class SimpleLeaderboard(Leaderboard):
|
||||
def __init__(self, v:User, meta:LeaderboardMeta, db:scoped_session, users_query:Query, column:Column):
|
||||
super().__init__(v, meta)
|
||||
self.db:scoped_session = db
|
||||
self.users_query:Query = users_query
|
||||
self.column:Column = column
|
||||
self._calculate()
|
||||
|
||||
def _calculate(self) -> None:
|
||||
self._all_users = self.users_query.order_by(self.column.desc()).limit(self.meta.limit).all()
|
||||
if self.v not in self._all_users:
|
||||
sq = self.db.query(User.id, self.column, func.rank().over(order_by=self.column.desc()).label("rank")).subquery()
|
||||
sq_data = self.db.query(sq.c.id, sq.c.column, sq.c.rank).filter(sq.c.id == self.v.id).limit(1).one()
|
||||
self._v_value:int = sq_data[1]
|
||||
self._v_position:int = sq_data[2]
|
||||
|
||||
@property
|
||||
def all_users(self) -> list[User]:
|
||||
return self._all_users
|
||||
|
||||
@property
|
||||
def v_position(self) -> int:
|
||||
return self._v_position
|
||||
|
||||
@property
|
||||
def v_value(self) -> int:
|
||||
return self._v_value
|
||||
|
||||
@property
|
||||
def value_func(self) -> Callable[[User], int]:
|
||||
return lambda u:getattr(u, self.column.name)
|
||||
|
||||
class _CountedAndRankedLeaderboard(Leaderboard):
|
||||
@classmethod
|
||||
def count_and_label(cls, criteria):
|
||||
return func.count(criteria).label("count")
|
||||
|
||||
@classmethod
|
||||
def rank_filtered_rank_label_by_desc(cls, criteria):
|
||||
return func.rank().over(order_by=func.count(criteria).desc()).label("rank")
|
||||
|
||||
class BadgeMarseyLeaderboard(_CountedAndRankedLeaderboard):
|
||||
def __init__(self, v:User, meta:LeaderboardMeta, db:scoped_session, column:Column):
|
||||
super().__init__(v, meta)
|
||||
self.db:scoped_session = db
|
||||
self.column = column
|
||||
self._calculate()
|
||||
|
||||
def _calculate(self):
|
||||
sq = self.db.query(self.column, self.count_and_label(self.column), self.rank_filtered_rank_label_by_desc(self.column)).group_by(self.column).subquery()
|
||||
sq_criteria = None
|
||||
if self.column == Badge.user_id:
|
||||
sq_criteria = User.id == sq.c.user_id
|
||||
elif self.column == Marsey.author_id:
|
||||
sq_criteria = User.id == sq.c.author_id
|
||||
else:
|
||||
raise ValueError("This leaderboard function only supports Badge.user_id and Marsey.author_id")
|
||||
leaderboard = self.db.query(User, sq.c.count).join(sq, sq_criteria).order_by(sq.c.count.desc())
|
||||
|
||||
position:Optional[tuple[int, int, int]] = self.db.query(User.id, sq.c.rank, sq.c.count).join(sq, sq_criteria).filter(User.id == self.v.id).one_or_none()
|
||||
if position and position[1]:
|
||||
self._v_position = position[1]
|
||||
self._v_value = position[2]
|
||||
else:
|
||||
self._v_position = leaderboard.count() + 1
|
||||
self._v_value = 0
|
||||
self._all_users = {k:v for k, v in leaderboard.limit(self.meta.limit).all()}
|
||||
|
||||
@property
|
||||
def all_users(self) -> list[User]:
|
||||
return list(self._all_users.keys())
|
||||
|
||||
@property
|
||||
def v_position(self) -> int:
|
||||
return self._v_position
|
||||
|
||||
@property
|
||||
def v_value(self) -> int:
|
||||
return self._v_value
|
||||
|
||||
@property
|
||||
def value_func(self) -> Callable[[User], int]:
|
||||
return lambda u:self._all_users[u]
|
||||
|
||||
class UserBlockLeaderboard(_CountedAndRankedLeaderboard):
|
||||
def __init__(self, v:User, meta:LeaderboardMeta, db:scoped_session, column:Column):
|
||||
super().__init__(v, meta)
|
||||
self.db:scoped_session = db
|
||||
self.column = column
|
||||
self._calculate()
|
||||
|
||||
def _calculate(self):
|
||||
if self.column != UserBlock.target_id:
|
||||
raise ValueError("This leaderboard function only supports UserBlock.target_id")
|
||||
sq = self.db.query(self.column, self.count_and_label(self.column)).group_by(self.column).subquery()
|
||||
leaderboard = self.db.query(User, sq.c.count).join(User, User.id == sq.c.target_id).order_by(sq.c.count.desc())
|
||||
|
||||
sq = self.db.query(self.column, self.count_and_label(self.column), self.rank_filtered_rank_label_by_desc(self.column)).group_by(self.column).subquery()
|
||||
position = self.db.query(sq.c.rank, sq.c.count).join(User, User.id == sq.c.target_id).filter(sq.c.target_id == self.v.id).limit(1).one_or_none()
|
||||
if not position: position = (leaderboard.count() + 1, 0)
|
||||
leaderboard = leaderboard.limit(self.meta.limit).all()
|
||||
self._all_users = {k:v for k, v in leaderboard}
|
||||
self._v_position = position[0]
|
||||
self._v_value = position[1]
|
||||
return (leaderboard, position[0], position[1])
|
||||
|
||||
@property
|
||||
def all_users(self) -> list[User]:
|
||||
return list(self._all_users.keys())
|
||||
|
||||
@property
|
||||
def v_position(self) -> int:
|
||||
return self._v_position
|
||||
|
||||
@property
|
||||
def v_value(self) -> int:
|
||||
return self._v_value
|
||||
|
||||
class RawSqlLeaderboard(Leaderboard):
|
||||
def __init__(self, meta:LeaderboardMeta, db:scoped_session, query:str) -> None: # should be LiteralString on py3.11+
|
||||
super().__init__(None, meta)
|
||||
self.db = db
|
||||
self._calculate(query)
|
||||
|
||||
def _calculate(self, query:str):
|
||||
self.result = {result[0]:list(result) for result in self.db.execute(query).all()}
|
||||
users = get_accounts_dict(self.result.keys(), db=self.db)
|
||||
if users is None:
|
||||
raise Exception("Some users don't exist when they should (was a user deleted?)")
|
||||
for user in users: # I know.
|
||||
self.result[user].append(users[user])
|
||||
|
||||
@property
|
||||
def all_users(self) -> list[User]:
|
||||
return [result[2] for result in self.result.values()]
|
||||
|
||||
@property
|
||||
def v_position(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
@property
|
||||
def v_value(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
@property
|
||||
def v_appears_in_ranking(self) -> bool:
|
||||
return True # we set this to True here to try and not grab the data
|
||||
|
||||
@property
|
||||
def user_func(self) -> Callable[[Any], User]:
|
||||
return lambda u:u
|
||||
|
||||
@property
|
||||
def value_func(self) -> Callable[[User], int]:
|
||||
return lambda u:self.result[u.id][1]
|
||||
|
||||
class ReceivedDownvotesLeaderboard(RawSqlLeaderboard):
|
||||
_query: Final[str] = """
|
||||
WITH cv_for_user AS (
|
||||
SELECT
|
||||
comments.author_id AS target_id,
|
||||
COUNT(*)
|
||||
FROM commentvotes cv
|
||||
JOIN comments ON comments.id = cv.comment_id
|
||||
WHERE vote_type = -1
|
||||
GROUP BY comments.author_id
|
||||
), sv_for_user AS (
|
||||
SELECT
|
||||
submissions.author_id AS target_id,
|
||||
COUNT(*)
|
||||
FROM votes sv
|
||||
JOIN submissions ON submissions.id = sv.submission_id
|
||||
WHERE vote_type = -1
|
||||
GROUP BY submissions.author_id
|
||||
)
|
||||
SELECT
|
||||
COALESCE(cvfu.target_id, svfu.target_id) AS target_id,
|
||||
(COALESCE(cvfu.count, 0) + COALESCE(svfu.count, 0)) AS count
|
||||
FROM cv_for_user cvfu
|
||||
FULL OUTER JOIN sv_for_user svfu
|
||||
ON cvfu.target_id = svfu.target_id
|
||||
ORDER BY count DESC LIMIT 25
|
||||
"""
|
||||
|
||||
def __init__(self, meta:LeaderboardMeta, db:scoped_session) -> None:
|
||||
super().__init__(meta, db, self._query)
|
||||
|
||||
class GivenUpvotesLeaderboard(RawSqlLeaderboard):
|
||||
_query: Final[str] = """
|
||||
SELECT
|
||||
COALESCE(cvbu.user_id, svbu.user_id) AS user_id,
|
||||
(COALESCE(cvbu.count, 0) + COALESCE(svbu.count, 0)) AS count
|
||||
FROM (SELECT user_id, COUNT(*) FROM votes WHERE vote_type = 1 GROUP BY user_id) AS svbu
|
||||
FULL OUTER JOIN (SELECT user_id, COUNT(*) FROM commentvotes WHERE vote_type = 1 GROUP BY user_id) AS cvbu
|
||||
ON cvbu.user_id = svbu.user_id
|
||||
ORDER BY count DESC LIMIT 25
|
||||
"""
|
||||
|
||||
def __init__(self, meta:LeaderboardMeta, db:scoped_session) -> None:
|
||||
super().__init__(meta, db, self._query)
|
|
@ -650,3 +650,9 @@ class User(Base):
|
|||
l = [i.strip() for i in self.custom_filter_list.split('\n')] if self.custom_filter_list else []
|
||||
l = [i for i in l if i]
|
||||
return l
|
||||
|
||||
# Permissions
|
||||
|
||||
@property
|
||||
def can_see_shadowbanned(self):
|
||||
return self.admin_level >= PERMS['USER_SHADOWBAN'] or self.shadowbanned
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
from os import environ, listdir
|
||||
import re
|
||||
from copy import deepcopy
|
||||
from json import loads
|
||||
from os import environ
|
||||
from typing import Final
|
||||
|
||||
from flask import request
|
||||
|
||||
from files.__main__ import db_session
|
||||
from files.classes.sub import Sub
|
||||
from files.classes.marsey import Marsey
|
||||
from flask import request
|
||||
|
||||
SITE = environ.get("DOMAIN", '').strip()
|
||||
SITE_ID = environ.get("SITE_ID", '').strip()
|
||||
|
@ -33,6 +35,8 @@ BUG_THREAD = 0
|
|||
WELCOME_MSG = f"Welcome to {SITE_TITLE}! Please read [the rules](/rules) first. Then [read some of our current conversations](/) and feel free to comment or post!\n\nWe encourage people to comment even if they aren't sure they fit in; as long as your comment follows [community rules](/rules), we are happy to have posters from all backgrounds, education levels, and specialties."
|
||||
ROLES={}
|
||||
|
||||
LEADERBOARD_LIMIT: Final[int] = 25
|
||||
|
||||
THEMES = {"TheMotte", "dramblr", "reddit", "transparent", "win98", "dark",
|
||||
"light", "coffee", "tron", "4chan", "midnight"}
|
||||
SORTS_COMMON = {
|
||||
|
@ -99,6 +103,7 @@ FEATURES = {
|
|||
|
||||
PERMS = {
|
||||
"DEBUG_LOGIN_TO_OTHERS": 3,
|
||||
"USER_SHADOWBAN": 2,
|
||||
}
|
||||
|
||||
AWARDS = {}
|
||||
|
|
|
@ -3,7 +3,7 @@ from typing import Callable, Iterable, List, Optional, Type, Union
|
|||
|
||||
from flask import g
|
||||
from sqlalchemy import and_, or_, func
|
||||
from sqlalchemy.orm import Query, selectinload
|
||||
from sqlalchemy.orm import Query, scoped_session, selectinload
|
||||
|
||||
from files.classes import *
|
||||
from files.helpers.const import AUTOJANNY_ID
|
||||
|
@ -95,6 +95,24 @@ def get_account(
|
|||
|
||||
return user
|
||||
|
||||
def get_accounts_dict(ids:Union[Iterable[str], Iterable[int]],
|
||||
v:Optional[User]=None, graceful=False,
|
||||
include_shadowbanned=True,
|
||||
db:Optional[scoped_session]=None) -> Optional[dict[int, User]]:
|
||||
if not db: db = g.db
|
||||
if not ids: return {}
|
||||
try:
|
||||
ids = set([int(id) for id in ids])
|
||||
except:
|
||||
if graceful: return None
|
||||
abort(404)
|
||||
|
||||
users = db.query(User).filter(User.id.in_(ids))
|
||||
if not (include_shadowbanned or (v and v.can_see_shadowbanned)):
|
||||
users = users.filter(User.shadowbanned == None)
|
||||
users = users.all()
|
||||
if len(users) != len(ids) and not graceful: abort(404)
|
||||
return {u.id:u for u in users}
|
||||
|
||||
def get_post(
|
||||
i:Union[str,int],
|
||||
|
|
59
files/helpers/services.py
Normal file
59
files/helpers/services.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
import sys
|
||||
|
||||
import gevent
|
||||
from pusher_push_notifications import PushNotifications
|
||||
from sqlalchemy.orm import scoped_session
|
||||
|
||||
from files.classes.leaderboard import (LeaderboardMeta, ReceivedDownvotesLeaderboard,
|
||||
GivenUpvotesLeaderboard)
|
||||
from files.helpers.assetcache import assetcache_path
|
||||
from files.helpers.const import PUSHER_ID, PUSHER_KEY, SITE_FULL, SITE_ID
|
||||
from files.__main__ import app, db_session
|
||||
|
||||
if PUSHER_ID != 'blahblahblah':
|
||||
beams_client = PushNotifications(instance_id=PUSHER_ID, secret_key=PUSHER_KEY)
|
||||
else:
|
||||
beams_client = None
|
||||
|
||||
def pusher_thread2(interests, notifbody, username):
|
||||
if not beams_client: return
|
||||
beams_client.publish_to_interests(
|
||||
interests=[interests],
|
||||
publish_body={
|
||||
'web': {
|
||||
'notification': {
|
||||
'title': f'New message from @{username}',
|
||||
'body': notifbody,
|
||||
'deep_link': f'{SITE_FULL}/notifications?messages=true',
|
||||
'icon': SITE_FULL + assetcache_path(f'images/{SITE_ID}/icon.webp'),
|
||||
}
|
||||
},
|
||||
'fcm': {
|
||||
'notification': {
|
||||
'title': f'New message from @{username}',
|
||||
'body': notifbody,
|
||||
},
|
||||
'data': {
|
||||
'url': '/notifications?messages=true',
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
_lb_received_downvotes_meta = LeaderboardMeta("Downvotes", "received downvotes", "received-downvotes", "downvotes", "downvoted")
|
||||
_lb_given_upvotes_meta = LeaderboardMeta("Upvotes", "given upvotes", "given-upvotes", "upvotes", "upvoting")
|
||||
|
||||
def leaderboard_thread():
|
||||
global lb_downvotes_received, lb_upvotes_given
|
||||
|
||||
db:scoped_session = db_session() # type: ignore
|
||||
|
||||
lb_downvotes_received = ReceivedDownvotesLeaderboard(_lb_received_downvotes_meta, db)
|
||||
lb_upvotes_given = GivenUpvotesLeaderboard(_lb_given_upvotes_meta, db)
|
||||
|
||||
db.close()
|
||||
sys.stdout.flush()
|
||||
|
||||
if app.config["ENABLE_SERVICES"]:
|
||||
gevent.spawn(leaderboard_thread())
|
|
@ -2,6 +2,8 @@ import qrcode
|
|||
import io
|
||||
import time
|
||||
import math
|
||||
|
||||
from files.classes.leaderboard import SimpleLeaderboard, BadgeMarseyLeaderboard, UserBlockLeaderboard, LeaderboardMeta
|
||||
from files.classes.views import ViewerRelationship
|
||||
from files.helpers.alerts import *
|
||||
from files.helpers.media import process_image
|
||||
|
@ -12,69 +14,14 @@ from files.helpers.assetcache import assetcache_path
|
|||
from files.helpers.contentsorting import apply_time_filter, sort_objects
|
||||
from files.mail import *
|
||||
from flask import *
|
||||
from files.__main__ import app, limiter, db_session
|
||||
from pusher_push_notifications import PushNotifications
|
||||
from files.__main__ import app, limiter
|
||||
from collections import Counter
|
||||
import gevent
|
||||
from sys import stdout
|
||||
|
||||
if PUSHER_ID != 'blahblahblah':
|
||||
beams_client = PushNotifications(instance_id=PUSHER_ID, secret_key=PUSHER_KEY)
|
||||
|
||||
def pusher_thread2(interests, notifbody, username):
|
||||
beams_client.publish_to_interests(
|
||||
interests=[interests],
|
||||
publish_body={
|
||||
'web': {
|
||||
'notification': {
|
||||
'title': f'New message from @{username}',
|
||||
'body': notifbody,
|
||||
'deep_link': f'{SITE_FULL}/notifications?messages=true',
|
||||
'icon': SITE_FULL + assetcache_path(f'images/{SITE_ID}/icon.webp'),
|
||||
}
|
||||
},
|
||||
'fcm': {
|
||||
'notification': {
|
||||
'title': f'New message from @{username}',
|
||||
'body': notifbody,
|
||||
},
|
||||
'data': {
|
||||
'url': '/notifications?messages=true',
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
stdout.flush()
|
||||
|
||||
def leaderboard_thread():
|
||||
global users9, users9_25, users13, users13_25
|
||||
|
||||
db = db_session()
|
||||
|
||||
votes1 = db.query(Submission.author_id, func.count(Submission.author_id)).join(Vote, Vote.submission_id==Submission.id).filter(Vote.vote_type==-1).group_by(Submission.author_id).order_by(func.count(Submission.author_id).desc()).all()
|
||||
votes2 = db.query(Comment.author_id, func.count(Comment.author_id)).join(CommentVote, CommentVote.comment_id==Comment.id).filter(CommentVote.vote_type==-1).group_by(Comment.author_id).order_by(func.count(Comment.author_id).desc()).all()
|
||||
votes3 = Counter(dict(votes1)) + Counter(dict(votes2))
|
||||
users8 = db.query(User).filter(User.id.in_(votes3.keys())).all()
|
||||
users9 = []
|
||||
for user in users8: users9.append((user, votes3[user.id]))
|
||||
users9 = sorted(users9, key=lambda x: x[1], reverse=True)
|
||||
users9_25 = users9[:25]
|
||||
|
||||
votes1 = db.query(Vote.user_id, func.count(Vote.user_id)).filter(Vote.vote_type==1).group_by(Vote.user_id).order_by(func.count(Vote.user_id).desc()).all()
|
||||
votes2 = db.query(CommentVote.user_id, func.count(CommentVote.user_id)).filter(CommentVote.vote_type==1).group_by(CommentVote.user_id).order_by(func.count(CommentVote.user_id).desc()).all()
|
||||
votes3 = Counter(dict(votes1)) + Counter(dict(votes2))
|
||||
users14 = db.query(User).filter(User.id.in_(votes3.keys())).all()
|
||||
users13 = []
|
||||
for user in users14:
|
||||
users13.append((user, votes3[user.id]-user.post_count-user.comment_count))
|
||||
users13 = sorted(users13, key=lambda x: x[1], reverse=True)
|
||||
users13_25 = users13[:25]
|
||||
|
||||
db.close()
|
||||
stdout.flush()
|
||||
|
||||
if app.config["ENABLE_SERVICES"]:
|
||||
gevent.spawn(leaderboard_thread())
|
||||
# warning: do not move currently. these have import-time side effects but
|
||||
# until this is refactored to be not completely awful, there's not really
|
||||
# a better option.
|
||||
from files.helpers.services import *
|
||||
|
||||
@app.get("/@<username>/upvoters/<uid>/posts")
|
||||
@admin_level_required(3)
|
||||
|
@ -412,73 +359,26 @@ def transfer_bux(v, username):
|
|||
|
||||
@app.get("/leaderboard")
|
||||
@admin_level_required(2)
|
||||
def leaderboard(v):
|
||||
def leaderboard(v:User):
|
||||
users:Query = g.db.query(User)
|
||||
if not v.can_see_shadowbanned:
|
||||
users = users.filter(User.shadowbanned == None)
|
||||
|
||||
users = g.db.query(User)
|
||||
coins = SimpleLeaderboard(v, LeaderboardMeta("Coins", "coins", "coins", "Coins", None), g.db, users, User.coins)
|
||||
subscribers = SimpleLeaderboard(v, LeaderboardMeta("Followers", "followers", "followers", "Followers", "followers"), g.db, users, User.stored_subscriber_count)
|
||||
posts = SimpleLeaderboard(v, LeaderboardMeta("Posts", "post count", "posts", "Posts", ""), g.db, users, User.post_count)
|
||||
comments = SimpleLeaderboard(v, LeaderboardMeta("Comments", "comment count", "comments", "Comments", "comments"), g.db, users, User.comment_count)
|
||||
received_awards = SimpleLeaderboard(v, LeaderboardMeta("Awards", "received awards", "awards", "Awards", None), g.db, users, User.received_award_count)
|
||||
coins_spent = SimpleLeaderboard(v, LeaderboardMeta("Spent in shop", "coins spent in shop", "spent", "Coins", None), g.db, users, User.coins_spent)
|
||||
truescore = SimpleLeaderboard(v, LeaderboardMeta("Truescore", "truescore", "truescore", "Truescore", None), g.db, users, User.truecoins)
|
||||
badges = BadgeMarseyLeaderboard(v, LeaderboardMeta("Badges", "badges", "badges", "Badges", None), g.db, Badge.user_id)
|
||||
blocks = UserBlockLeaderboard(v, LeaderboardMeta("Blocked", "most blocked", "blocked", "Blocked By", "blockers"), g.db, UserBlock.target_id)
|
||||
|
||||
users1 = users.order_by(User.coins.desc()).limit(25).all()
|
||||
sq = g.db.query(User.id, func.rank().over(order_by=User.coins.desc()).label("rank")).subquery()
|
||||
pos1 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1]
|
||||
# note: lb_downvotes_received and lb_upvotes_given are global variables
|
||||
# that are populated by leaderboard_thread() in files.helpers.services
|
||||
leaderboards = [coins, coins_spent, truescore, subscribers, posts, comments, received_awards, badges, blocks, lb_downvotes_received, lb_upvotes_given]
|
||||
|
||||
users2 = users.order_by(User.stored_subscriber_count.desc()).limit(25).all()
|
||||
sq = g.db.query(User.id, func.rank().over(order_by=User.stored_subscriber_count.desc()).label("rank")).subquery()
|
||||
pos2 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1]
|
||||
|
||||
users3 = users.order_by(User.post_count.desc()).limit(25).all()
|
||||
sq = g.db.query(User.id, func.rank().over(order_by=User.post_count.desc()).label("rank")).subquery()
|
||||
pos3 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1]
|
||||
|
||||
users4 = users.order_by(User.comment_count.desc()).limit(25).all()
|
||||
sq = g.db.query(User.id, func.rank().over(order_by=User.comment_count.desc()).label("rank")).subquery()
|
||||
pos4 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1]
|
||||
|
||||
users5 = users.order_by(User.received_award_count.desc()).limit(25).all()
|
||||
sq = g.db.query(User.id, func.rank().over(order_by=User.received_award_count.desc()).label("rank")).subquery()
|
||||
pos5 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1]
|
||||
|
||||
users6 = None
|
||||
pos6 = None
|
||||
|
||||
users7 = users.order_by(User.coins_spent.desc()).limit(25).all()
|
||||
sq = g.db.query(User.id, func.rank().over(order_by=User.coins_spent.desc()).label("rank")).subquery()
|
||||
pos7 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1]
|
||||
|
||||
try:
|
||||
pos9 = [x[0].id for x in users9].index(v.id)
|
||||
pos9 = (pos9+1, users9[pos9][1])
|
||||
except: pos9 = (len(users9)+1, 0)
|
||||
|
||||
users10 = users.order_by(User.truecoins.desc()).limit(25).all()
|
||||
sq = g.db.query(User.id, func.rank().over(order_by=User.truecoins.desc()).label("rank")).subquery()
|
||||
pos10 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1]
|
||||
|
||||
sq = g.db.query(Badge.user_id, func.count(Badge.user_id).label("count"), func.rank().over(order_by=func.count(Badge.user_id).desc()).label("rank")).group_by(Badge.user_id).subquery()
|
||||
users11 = g.db.query(User, sq.c.count).join(sq, User.id==sq.c.user_id).order_by(sq.c.count.desc())
|
||||
pos11 = g.db.query(User.id, sq.c.rank, sq.c.count).join(sq, User.id==sq.c.user_id).filter(User.id == v.id).one_or_none()
|
||||
if pos11: pos11 = (pos11[1],pos11[2])
|
||||
else: pos11 = (users11.count()+1, 0)
|
||||
users11 = users11.limit(25).all()
|
||||
|
||||
if pos11[1] < 25 and v not in (x[0] for x in users11):
|
||||
pos11 = (26, pos11[1])
|
||||
|
||||
users12 = None
|
||||
pos12 = None
|
||||
|
||||
try:
|
||||
pos13 = [x[0].id for x in users13].index(v.id)
|
||||
pos13 = (pos13+1, users13[pos13][1])
|
||||
except: pos13 = (len(users13)+1, 0)
|
||||
|
||||
users14 = users.order_by(User.winnings.desc()).limit(25).all()
|
||||
sq = g.db.query(User.id, func.rank().over(order_by=User.winnings.desc()).label("rank")).subquery()
|
||||
pos14 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1]
|
||||
|
||||
users15 = users.order_by(User.winnings).limit(25).all()
|
||||
sq = g.db.query(User.id, func.rank().over(order_by=User.winnings).label("rank")).subquery()
|
||||
pos15 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1]
|
||||
|
||||
return render_template("leaderboard.html", v=v, users1=users1, pos1=pos1, users2=users2, pos2=pos2, users3=users3, pos3=pos3, users4=users4, pos4=pos4, users5=users5, pos5=pos5, users6=users6, pos6=pos6, users7=users7, pos7=pos7, users9=users9_25, pos9=pos9, users10=users10, pos10=pos10, users11=users11, pos11=pos11, users12=users12, pos12=pos12, users13=users13_25, pos13=pos13, users14=users14, pos14=pos14, users15=users15, pos15=pos15)
|
||||
return render_template("leaderboard.html", v=v, leaderboards=leaderboards)
|
||||
|
||||
@app.get("/@<username>/css")
|
||||
def get_css(username):
|
||||
|
|
1
files/templates/admin/shadowbanned_tooltip.html
Normal file
1
files/templates/admin/shadowbanned_tooltip.html
Normal file
|
@ -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.shadowbanner}}{% if user.ban_reason %} for "{{user.ban_reason}}"{% endif %}'></i>{% endif %}
|
|
@ -1,484 +1,62 @@
|
|||
{% extends "settings2.html" %}
|
||||
|
||||
{% block pagetitle %}Leaderboard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<pre class="d-none d-md-inline-block"></pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by coins</h5>
|
||||
<pre></pre>
|
||||
<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>Coins</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users1 %}
|
||||
<tr {% if v.id == user.id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user.namecolor}};font-weight:bold" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td>{{user.coins}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos1 > 25 %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos1}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{v.coins}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by coins spent in shop</h5>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<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>Coins</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{% for user in users7 %}
|
||||
<tr {% if v.id == user.id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user.namecolor}};font-weight:bold" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td>{{user.coins_spent}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos7 > 25 %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos7}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{v.coins_spent}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by truescore</h5>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<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>Truescore</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="followers-table">
|
||||
{% for user in users10 %}
|
||||
<tr {% if v.id == user.id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user.namecolor}};font-weight:bold" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td>{{user.truecoins}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos10 > 25 %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos10}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{v.truecoins}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by followers</h5>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<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>Followers</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users2 %}
|
||||
<tr {% if v.id == user.id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user.namecolor}};font-weight:bold" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td>{{user.stored_subscriber_count}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos2 > 25 %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos2}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{v.stored_subscriber_count}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by post count</h5>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<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>Posts</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users3 %}
|
||||
<tr {% if v.id == user.id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user.namecolor}};font-weight:bold" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td>{{user.post_count}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos3 > 25 %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos3}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{v.post_count}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by comment count</h5>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<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>Comments</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users4 %}
|
||||
<tr {% if v.id == user.id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user.namecolor}};font-weight:bold" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td>{{user.comment_count}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos4 > 25 %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos4}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{v.comment_count}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by received awards</h5>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<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>Awards</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users5 %}
|
||||
<tr {% if v.id == user.id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user.namecolor}};font-weight:bold" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td>{{user.received_award_count}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos5 > 25 %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos5}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{v.received_award_count}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by received downvotes</h5>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
|
||||
<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>Downvotes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="followers-table">
|
||||
{% for user in users9 %}
|
||||
<tr {% if v.id == user[0].id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user[0].namecolor}};font-weight:bold" href="/@{{user[0].username}}"><img loading="lazy" src="{{user[0].profile_url}}" class="pp20"><span {% if user[0].patron %}class="patron" style="background-color:#{{user[0].namecolor}}"{% endif %}>{{user[0].username}}</span></a></td>
|
||||
<td>{{user[1]}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos9 and (pos9[0] > 25 or not pos9[1]) %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos9[0]}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{pos9[1]}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by badges</h5>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
|
||||
<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>Badges</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="followers-table">
|
||||
{% for user in users11 %}
|
||||
<tr {% if v.id == user[0].id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user[0].namecolor}};font-weight:bold" href="/@{{user[0].username}}"><img loading="lazy" src="{{user[0].profile_url}}" class="pp20"><span {% if user[0].patron %}class="patron" style="background-color:#{{user[0].namecolor}}"{% endif %}>{{user[0].username}}</span></a></td>
|
||||
<td>{{user[1]}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos11 and (pos11[0] > 25 or not pos11[1]) %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos11[0]}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{pos11[1]}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
{% if users6 %}
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by based count</h5>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<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>Based count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users6 %}
|
||||
<tr {% if v.id == user.id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user.namecolor}};font-weight:bold" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td>{{user.basedcount}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos6 > 25 %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos6}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{v.basedcount}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if users12 %}
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by marseys made</h5>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
|
||||
<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>Marseys</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="followers-table">
|
||||
{% for user in users12 %}
|
||||
<tr {% if v.id == user[0].id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user[0].namecolor}};font-weight:bold" href="/@{{user[0].username}}"><img loading="lazy" src="{{user[0].profile_url}}" class="pp20"><span {% if user[0].patron %}class="patron" style="background-color:#{{user[0].namecolor}}"{% endif %}>{{user[0].username}}</span></a></td>
|
||||
<td>{{user[1]}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos12 and (pos12[0] > 25 or not pos12[1]) %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos12[0]}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{pos12[1]}}</td>
|
||||
</tr>
|
||||
<div id="leaderboard-contents" style="text-align: center; margin-bottom: 1.5rem; font-size: 1.2rem;">
|
||||
{% for lb in leaderboards %}
|
||||
{% if lb %}
|
||||
<a href="#leaderboard-{{lb.html_id}}">{{lb.meta.header_name}}</a>{% if not loop.last %} •{% endif %}
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if users13 %}
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by upvotes given</h5>
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
|
||||
<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>Upvotes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="followers-table">
|
||||
{% for user in users13 %}
|
||||
<tr {% if v.id == user[0].id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user[0].namecolor}};font-weight:bold" href="/@{{user[0].username}}"><img loading="lazy" src="{{user[0].profile_url}}" class="pp20"><span {% if user[0].patron %}class="patron" style="background-color:#{{user[0].namecolor}}"{% endif %}>{{user[0].username}}</span></a></td>
|
||||
<td>{{user[1]}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos13 and (pos13[0] > 25 or not pos13[1]) %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos13[0]}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{pos13[1]}}</td>
|
||||
</tr>
|
||||
{% macro format_user_in_table(user, style, position_no, value, user_relative_url) %}
|
||||
{% set value = value | int %}
|
||||
<tr {{style | safe}}>
|
||||
<td>{{position_no}}</td>
|
||||
<td>{% include "user_in_table.html" %}</td>
|
||||
{% if user_relative_url is not none %}
|
||||
<td><a href="/@{{user.username}}/{{user_relative_url}}">{{"{:,}".format(value)}}</a></td>
|
||||
{% else %}
|
||||
<td>{{"{:,}".format(value)}}</td>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endmacro %}
|
||||
|
||||
<h5 style="font-weight:bold;text-align: center">Top 25 by winnings</h5>
|
||||
<pre></pre>
|
||||
<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>Winnings</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users14 %}
|
||||
<tr {% if v.id == user.id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user.namecolor}};font-weight:bold" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td>{{user.winnings}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if pos14 > 25 %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos14}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{v.winnings}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% macro leaderboard_table(lb) %}
|
||||
<h5 class="font-weight-bolder text-center pt-2 pb-3"><span id="leaderboard-{{lb.meta.html_id}}">Top {{lb.limit}} {% if lb.meta.table_header_name != 'most blocked' %}by{% endif %} {{lb.meta.table_header_name}}</span></h5>
|
||||
<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>{{lb.meta.table_column_name}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in lb.all_users %}
|
||||
{% set user2 = lb.user_func(user) %}
|
||||
{% if v.id == user2.id %}
|
||||
{% set style="class=\"self\"" %}
|
||||
{% endif %}
|
||||
{{format_user_in_table(user2, style, loop.index, lb.value_func(user), lb.meta.user_relative_url)}}
|
||||
{% endfor %}
|
||||
{% if lb.v_position and not lb.v_appears_in_ranking %}
|
||||
{{format_user_in_table(v, "style=\"border-top:2px solid var(--primary)\"", lb.v_position, lb.v_value, lb.meta.user_relative_url)}}
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h5 style="font-weight:bold;text-align: center">Bottom 25 by winnings</h5>
|
||||
<pre></pre>
|
||||
<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>Winnings</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users15 %}
|
||||
<tr {% if v.id == user.id %}class="self"{% endif %}>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user.namecolor}};font-weight:bold" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td>{{user.winnings}}</td>
|
||||
</tr>
|
||||
{% for lb in leaderboards %}
|
||||
{% if lb %}
|
||||
{{leaderboard_table(lb)}}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if pos15 > 25 %}
|
||||
<tr style="border-top:2px solid var(--primary)">
|
||||
<td>{{pos15}}</td>
|
||||
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img loading="lazy" src="{{v.profile_url}}" class="pp20"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></a></td>
|
||||
<td>{{v.winnings}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<a id="leader--top-btn" href="#leaderboard-contents"
|
||||
style="position: fixed; bottom: 5rem; right: 2rem; font-size: 3rem;">
|
||||
<i class="fas fa-arrow-alt-circle-up"></i>
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
|
17
files/templates/user_in_table.html
Normal file
17
files/templates/user_in_table.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{%- include 'admin/shadowbanned_tooltip.html' -%}
|
||||
{% macro username_display(user, name=None, distinguish=0, display_class="") %}
|
||||
{% set display_name = user.username %}
|
||||
{% set display_color_fg = "ffffff" if user.patron else user.name_color %}
|
||||
{% set display_color_bg = user.name_color if (user.patron and not distinguish) else None %}
|
||||
{% set display_class = "mod" if distinguish else ("rounded-bg" if user.patron else "") %}
|
||||
<span class="font-weight-bold {{display_class}}" style="color: #{{display_color_fg}};{% if display_color_bg %} background-color:#{{display_color_bg}};{% endif %}">{{display_name}}</span>
|
||||
{% endmacro %}
|
||||
|
||||
{% if user %}
|
||||
<a data-sort-key="{{user.username.lower()}}" href="/@{{user.username}}">
|
||||
<span class="profile-pic-20-wrapper">
|
||||
<img loading="lazy" src="{{user.profile_url}}" class="pp20">
|
||||
</span>
|
||||
{{username_display(user)}}
|
||||
</a>
|
||||
{% endif %}
|
Loading…
Add table
Add a link
Reference in a new issue