remotes/1693045480750635534/spooky-22
Aevann1 2022-01-24 01:06:34 +02:00
parent c9092e6fcd
commit 8b381284c6
16 changed files with 114 additions and 3398 deletions

View File

@ -61,7 +61,7 @@ app.config['DESCRIPTION'] = environ.get("DESCRIPTION", "rdrama.net caters to dra
r=redis.Redis(host=environ.get("REDIS_URL", "redis://localhost"), decode_responses=True, ssl_cert_reqs=None)
def get_CF() -> str:
def get_CF():
with app.app_context():
return request.headers.get('CF-Connecting-IP')

View File

@ -13,4 +13,5 @@ from .slots import *
from .subscriptions import *
from files.__main__ import app
from .mod_logs import *
from .award import *
from .award import *
from .marsey import *

View File

@ -271,7 +271,7 @@ class Comment(Base):
return data
def award_count(self, kind) -> int:
def award_count(self, kind):
return len([x for x in self.awards if x.kind == kind])
@property

View File

@ -1,13 +1,14 @@
from sqlalchemy import *
from files.__main__ import Base
from files.helpers.lazy import lazy
class Marsey(Base):
__tablename__ = "marseys"
name = Column(String, primary_key=True)
author = Column(Integer, ForeignKey("users.id"))
tags = Column(String, ForeignKey("users.id"))
count = Column(Integer)
author_id = Column(Integer, ForeignKey("users.id"))
tags = Column(String)
count = Column(Integer, default=0)
def __repr__(self):
return f"<Marsey(name={self.name})>"

View File

@ -335,7 +335,7 @@ class Submission(Base):
return data
def award_count(self, kind) -> int:
def award_count(self, kind):
return len([x for x in self.awards if x.kind == kind])
@lazy

View File

@ -99,10 +99,10 @@ SLURS = {
single_words = "|".join([slur.lower() for slur in SLURS.keys()])
SLUR_REGEX = re.compile(rf"(?i)((?<=\s|>)|^)({single_words})((?=[\s<,.]|s[\s<,.])|$)")
def sub_matcher(match: re.Match) -> str:
def sub_matcher(match: re.Match):
return SLURS[match.group(0).lower()]
def censor_slurs(body: str, logged_user) -> str:
def censor_slurs(body: str, logged_user):
if not logged_user or logged_user.slurreplacer: body = SLUR_REGEX.sub(sub_matcher, body)
return body

View File

@ -177,9 +177,7 @@ def sanitize(sanitized, noimages=False, alert=False, comment=False, edit=False):
sanitized = re.sub('\|\|(.*?)\|\|', r'<span class="spoiler">\1</span>', sanitized)
if comment:
with open("marseys.json", 'r') as f: marsey_count = loads(f.read().replace("'",'"'))
marseys_used = set()
if comment: marseys_used = set()
emojis = list(re.finditer("[^a]>\s*(:[!#]{0,2}\w+:\s*)+<\/", sanitized))
if len(emojis) > 20: edit = True
@ -261,9 +259,9 @@ def sanitize(sanitized, noimages=False, alert=False, comment=False, edit=False):
sanitized = re.sub('<p>(https:\/\/[^ <>]*)', r'<p><a target="_blank" rel="nofollow noopener noreferrer" href="\1">\1</a></p>', sanitized)
if comment:
for emoji in marseys_used:
if emoji in marsey_count: marsey_count[emoji]["count"] += 1
with open('marseys.json', 'w') as f: dump(marsey_count, f)
for marsey in g.db.query(Marsey).filter(Marsey.name.in_(marseys_used)).all():
marsey.count += 1
g.db.add(marsey)
return sanitized

View File

@ -26,19 +26,6 @@ CF_HEADERS = {"Authorization": f"Bearer {CF_KEY}", "Content-Type": "application/
month = datetime.now().strftime('%B')
@app.get("/refund")
@admin_level_required(3)
def refund(v):
users = (x[0] for x in g.db.query(AwardRelationship.user_id).filter(AwardRelationship.submission_id == None, AwardRelationship.comment_id == None, AwardRelationship.kind.in_(('snow','gingerbread','lights','candycane','fireplace','haunt','upsidedown','stab','spiders','fog'))).all())
for uid in users:
user = get_account(uid)
user.coins += 500
g.db.add(user)
g.db.commit()
return 'sex'
@app.post("/@<username>/make_admin")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@ -1217,7 +1204,7 @@ def admin_distinguish_comment(c_id, v):
@app.get("/admin/dump_cache")
@admin_level_required(2)
def admin_dump_cache(v):
# cache.clear()
cache.clear()
return {"message": "Internal cache cleared."}

View File

@ -16,6 +16,10 @@ IMGUR_KEY = environ.get("IMGUR_KEY").strip()
if PUSHER_ID: beams_client = PushNotifications(instance_id=PUSHER_ID, secret_key=PUSHER_KEY)
CF_KEY = environ.get("CF_KEY", "").strip()
CF_ZONE = environ.get("CF_ZONE", "").strip()
CF_HEADERS = {"Authorization": f"Bearer {CF_KEY}", "Content-Type": "application/json"}
@app.get("/comment/<cid>")
@app.get("/post/<pid>/<anything>/<cid>")
@app.get("/logged_out/comment/<cid>")
@ -154,26 +158,9 @@ def api_comment(v):
body = request.values.get("body", "").strip()[:10000]
if v.admin_level == 3:
if parent_post.id == 37749:
with open(f"snappy_{SITE_NAME}.txt", "a") as f:
f.write('\n{[para]}\n' + body)
elif request.files.get("file"):
if parent_post.id == 37833:
try: badge_body = loads(body.lower())
except: return {"error": "You didn't follow the format retard"}, 500
badge_number = str(len(listdir('files/assets/images/badges'))+1)
with open("badges.json", 'r') as f: badges = loads(f.read())
badges[badge_number] = badge_body
elif v.id in (CARP_ID,AEVANN_ID) and parent_post.id == 37838:
try:
marsey_dict = list(loads(body.lower()).items())
marsey_key = marsey_dict[0][0]
marsey_body = marsey_dict[0][1]
marsey_body["count"] = 0
except: return {"error": "You didn't follow the format retard"}, 400
with open("marseys.json", 'r') as f: marseys = loads(f.read().replace("'",'"'))
marseys[marsey_key] = marsey_body
if v.admin_level == 3 and parent_post.id == 37749:
with open(f"snappy_{SITE_NAME}.txt", "a") as f:
f.write('\n{[para]}\n' + body)
if v.marseyawarded:
marregex = list(re.finditer("^(:[!#]{0,2}m\w+:\s*)+$", body))
@ -197,7 +184,6 @@ def api_comment(v):
if file.content_type.startswith('image/'):
image = process_image(file)
if image == "": return {"error":"Image upload failed"}
body += f"\n\n![]({image})"
if v.admin_level == 3:
if parent_post.id == 37696:
filename = 'files/assets/images/Drama/sidebar/' + str(len(listdir('files/assets/images/Drama/sidebar'))+1) + '.webp'
@ -205,14 +191,31 @@ def api_comment(v):
elif parent_post.id == 37697:
filename = 'files/assets/images/Drama/banners/' + str(len(listdir('files/assets/images/Drama/banners'))+1) + '.webp'
process_image(file, filename)
elif parent_post.id == 37833:
filename = f'files/assets/images/badges/{badge_number}.webp'
elif parent_post.id == 50:
try: badge_def = loads(body.lower())
except: return {"error": "You didn't follow the format retard"}, 500
name = badge_def["name"]
badge = g.db.query(BadgeDef).filter_by(name=name).first()
if not badge:
badge = BadgeDef(name=name, description=badge_def["description"])
g.db.add(badge)
g.db.flush()
filename = f'files/assets/images/badges/{badge.id}.webp'
process_image(file, filename, 200)
with open('badges.json', 'w') as f: dump(badges, f)
elif v.id in (CARP_ID,AEVANN_ID) and parent_post.id == 37838:
filename = f'files/assets/images/emojis/{marsey_key}.webp'
requests.post(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/purge_cache', headers=CF_HEADERS, data={'files': [f"https://{request.host}/static/assets/images/badges/{badge.id}.webp"]})
elif parent_post.id == 49:
try: marsey = loads(body.lower())
except Exception as e:
print(body.lower(), flush=True)
return {"error": f"{e}"}, 500
name = marsey["name"]
if not g.db.query(Marsey.name).filter_by(name=name).first():
marsey = Marsey(name=marsey["name"], author_id=marsey["author_id"], tags=marsey["tags"], count=0)
g.db.add(marsey)
filename = f'files/assets/images/emojis/{name}.webp'
process_image(file, filename, 200)
with open('marseys.json', 'w') as f: dump(marseys, f)
requests.post(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/purge_cache', headers=CF_HEADERS, data={'files': [f"https://{request.host}/static/assets/images/emojis/{name}.webp"]})
body += f"\n\n![]({image})"
elif file.content_type.startswith('video/'):
file.save("video.mp4")
with open("video.mp4", 'rb') as f:

View File

@ -17,6 +17,16 @@ from urllib.parse import ParseResult, urlunparse, urlparse, quote, unquote
from os import path
import requests
db = db_session()
marseys = tuple(f':#{x[0]}:' for x in db.query(Marsey.name).all())
db.close()
if path.exists(f'snappy_{SITE_NAME}.txt'):
with open(f'snappy_{SITE_NAME}.txt', "r") as f:
if SITE == 'pcmemes.net': snappyquotes = tuple(f.read().split("{[para]}"))
else: snappyquotes = tuple(f.read().split("{[para]}")) + marseys
else: snappyquotes = marseys
IMGUR_KEY = environ.get("IMGUR_KEY").strip()
CF_KEY = environ.get("CF_KEY", "").strip()
@ -1004,13 +1014,7 @@ def submit_post(v):
elif v.id == LAWLZ_ID:
if random.random() < 0.5: body = "wow, this lawlzpost sucks!"
else: body = "wow, a good lawlzpost for once!"
elif path.exists(f'snappy_{SITE_NAME}.txt'):
with open(f'snappy_{SITE_NAME}.txt', "r") as f:
snappyquotes = f.read().split("{[para]}")
if request.host != 'pcmemes.net':
with open("marseys.json", 'r') as f: marseys = loads(f.read().replace("'",'"')).keys()
snappyquotes += [f':#{x}:' for x in marseys]
body = random.choice(snappyquotes)
else: body = random.choice(snappyquotes)
body += "\n\n"
if new_post.url:

View File

@ -18,9 +18,16 @@ def privacy(v):
@app.get("/marseys")
@auth_required
def marseys(v):
with open("marseys.json", 'r') as f: marsey_count = list(loads(f.read().replace("'",'"')).items())
marsey_count = sorted(marsey_count, key=lambda x: list(x[1].values())[2], reverse=True)
return render_template("marseys.html", v=v, marseys=marsey_count)
marseys = g.db.query(Marsey, User).join(User, User.id==Marsey.author_id).order_by(Marsey.count.desc())
return render_template("marseys.html", v=v, marseys=marseys)
@app.get("/marsey_list")
@cache.memoize(timeout=600)
def marsey_list():
marseys = {}
for marsey, user in g.db.query(Marsey, User.username).join(User, User.id==Marsey.author_id).order_by(Marsey.count.desc()):
marseys[marsey.name] = f"{user} {marsey.tags}"
return marseys
@app.get("/terms")
@app.get("/logged_out/terms")
@ -49,9 +56,7 @@ def participation_stats(v):
day = now - 86400
with open("marseys.json", 'r') as f: marseys = loads(f.read().replace("'",'"'))
data = {"marseys": len(marseys),
data = {"marseys": g.db.query(Marsey.name).count(),
"users": g.db.query(User.id).count(),
"private_users": g.db.query(User.id).filter_by(is_private=True).count(),
"banned_users": g.db.query(User.id).filter(User.is_banned > 0).count(),
@ -85,15 +90,9 @@ def participation_stats(v):
@app.get("/chart")
@auth_required
def chart(v):
days = int(request.values.get("days", 0))
file = cached_chart(days)
return send_file(file)
@cache.memoize(timeout=86400)
def cached_chart(days):
def chart():
days = int(request.values.get("days", 0))
now = time.gmtime()
midnight_this_morning = time.struct_time((now.tm_year,
now.tm_mon,
@ -165,7 +164,7 @@ def cached_chart(days):
plt.savefig(file)
plt.clf()
return file
return send_file(file)
@app.get("/patrons")
@ -244,6 +243,7 @@ def log_item(id, v):
return render_template("log.html", v=v, actions=[action], next_exists=False, page=1, action=action, admins=admins, types=types)
@app.get("/static/assets/favicon.ico")
@cache.memoize(timeout=86400)
def favicon():
return send_file(f"./assets/images/{SITE_NAME}/icon.webp")
@ -345,6 +345,7 @@ def images(path):
return resp
@app.get("/robots.txt")
@cache.memoize(timeout=86400)
def robots_txt():
return send_file("assets/robots.txt")
@ -371,11 +372,6 @@ def badges(v):
return render_template("badges.html", v=v, badges=badges)
@app.get("/marsey_list")
@auth_required
def marsey_list(v):
with open("marseys.json", 'r') as f: return loads(f.read().replace("'",'"'))
@app.get("/blocks")
@auth_required
def blocks(v):

View File

@ -16,7 +16,7 @@ import gevent
if PUSHER_ID: beams_client = PushNotifications(instance_id=PUSHER_ID, secret_key=PUSHER_KEY)
def leaderboard_thread():
global users9, users9_25, users13, userss13_25, users15, userss15_25
global users9, users9_25, users13, users13_25
db = db_session()
@ -29,31 +29,15 @@ def leaderboard_thread():
users9 = sorted(users9, key=lambda x: x[1], reverse=True)
users9_25 = users9[:25]
if SITE_NAME == 'Drama':
users13 = {}
with open("marseys.json", 'r') as f: authors = (x for x in loads(f.read().replace("'",'"')).values())
for x in authors:
if x["author"] in users13: users13[x["author"]] += 1
else: users13[x["author"]] = 1
users13.pop('unknown','anton-d')
users132 = db.query(User).filter(func.lower(User.username).in_(users13.keys())).all()
users133 = []
for user in users132:
users133.append((user, users13[user.username.lower()]))
users13 = sorted(users133, key=lambda x: x[1], reverse=True)
userss13_25 = users13[:25]
else: userss13_25 = None
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()
users15 = []
users13 = []
for user in users14:
users15.append((user, votes3[user.id]-user.post_count-user.comment_count))
users15 = sorted(users15, key=lambda x: x[1], reverse=True)
userss15_25 = users15[:25]
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()
@ -352,23 +336,29 @@ def leaderboard(v):
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()).limit(25).all()
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 SITE_NAME == 'Drama':
try:
pos13 = [x[0].id for x in users13].index(v.id)
pos13 = (pos13+1, users13[pos13][1])
except: pos13 = (len(users13)+1, 0)
else: pos13 = None
sq = g.db.query(Marsey.author_id, func.count(Marsey.author_id).label("count"), func.rank().over(order_by=func.count(Marsey.author_id).desc()).label("rank")).group_by(Marsey.author_id).subquery()
users12 = g.db.query(User, sq.c.count).join(sq, User.id==sq.c.author_id).order_by(sq.c.count.desc())
pos12 = g.db.query(User.id, sq.c.rank, sq.c.count).join(sq, User.id==sq.c.author_id).filter(User.id == v.id).one_or_none()
if pos12: pos12 = (pos12[1],pos12[2])
else: pos12 = (users12.count()+1, 0)
users12 = users12.limit(25).all()
else:
users12 = None
pos12 = None
try:
pos15 = [x[0].id for x in users15].index(v.id)
pos15 = (pos15+1, users15[pos15][1])
except: pos15 = (len(users15)+1, 0)
pos13 = [x[0].id for x in users13].index(v.id)
pos13 = (pos13+1, users13[pos13][1])
except: pos13 = (len(users13)+1, 0)
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, users13=userss13_25, pos13=pos13, users15=userss15_25, pos15=pos15)
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)
@app.get("/@<username>/css")
def get_css(username):

View File

@ -86,7 +86,7 @@
</div>
</div>
<script src="/static/assets/js/emoji_modal.js?a=226"></script>
<script src="/static/assets/js/emoji_modal.js?a=227"></script>
<style>
a.emojitab {

View File

@ -259,7 +259,7 @@
<td style="font-weight: bold">{{user[1]}}</td>
</tr>
{% endfor %}
{% if pos9 and pos9[0] > 25 %}
{% if pos9 and (pos9[0] > 25 or not pos9[1]) %}
<tr style="border-top:2px solid var(--primary)">
<td style="font-weight:bold">{{pos9[0]}}</td>
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img alt="@{{v.username}}'s profile picture" 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>
@ -296,7 +296,7 @@
<td style="font-weight: bold">{{user[1]}}</td>
</tr>
{% endfor %}
{% if pos11 and pos11[0] > 25 %}
{% if pos11 and (pos11[0] > 25 or not pos11[1]) %}
<tr style="border-top:2px solid var(--primary)">
<td style="font-weight:bold">{{pos11[0]}}</td>
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img alt="@{{v.username}}'s profile picture" 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>
@ -343,7 +343,7 @@
</table>
{% endif %}
{% if users13 %}
{% if users12 %}
<pre>
@ -363,25 +363,25 @@
</tr>
</thead>
<tbody id="followers-table">
{% for user in users13 %}
{% for user in users12 %}
<tr {% if v.id == user[0].id %}class="self"{% endif %}>
<td style="font-weight: bold">{{loop.index}}</td>
<td><a style="color:#{{user[0].namecolor}};font-weight:bold" href="/@{{user[0].username}}"><img alt="@{{user[0].username}}'s profile picture" 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 style="font-weight: bold">{{user[1]}}</td>
</tr>
{% endfor %}
{% if pos13 and pos13[0] > 25 %}
{% if pos12 and (pos12[0] > 25 or not pos12[1]) %}
<tr style="border-top:2px solid var(--primary)">
<td style="font-weight:bold">{{pos13[0]}}</td>
<td style="font-weight:bold">{{pos12[0]}}</td>
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img alt="@{{v.username}}'s profile picture" 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 style="font-weight:bold">{{pos13[1]}}</td>
<td style="font-weight:bold">{{pos12[1]}}</td>
</tr>
{% endif %}
</tbody>
</table>
{% endif %}
{% if users15 %}
{% if users13 %}
<pre>
@ -401,18 +401,18 @@
</tr>
</thead>
<tbody id="followers-table">
{% for user in users15 %}
{% for user in users13 %}
<tr {% if v.id == user[0].id %}class="self"{% endif %}>
<td style="font-weight: bold">{{loop.index}}</td>
<td><a style="color:#{{user[0].namecolor}};font-weight:bold" href="/@{{user[0].username}}"><img alt="@{{user[0].username}}'s profile picture" 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 style="font-weight: bold">{{user[1]}}</td>
</tr>
{% endfor %}
{% if pos15 and pos15[0] > 25 %}
{% if pos13 and (pos13[0] > 25 or not pos13[1]) %}
<tr style="border-top:2px solid var(--primary)">
<td style="font-weight:bold">{{pos15[0]}}</td>
<td style="font-weight:bold">{{pos13[0]}}</td>
<td><a style="color:#{{v.namecolor}};font-weight:bold" href="/@{{v.username}}"><img alt="@{{v.username}}'s profile picture" 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 style="font-weight:bold">{{pos15[1]}}</td>
<td style="font-weight:bold">{{pos13[1]}}</td>
</tr>
{% endif %}
</tbody>

View File

@ -15,13 +15,13 @@
</tr>
</thead>
<tbody id="followers-table">
{% for k, val in marseys %}
{% for marsey, author in marseys %}
<tr>
<td style="font-weight: bold">{{loop.index}}</td>
<td style="font-weight: bold">{{k}}</td>
<td><img class="marsey" loading="lazy" data-bs-toggle="tooltip" alt=":{{k}}:" title=":{{k}}:" delay="0" src="/static/assets/images/emojis/{{k}}.webp?a=1007" ></td>
<td style="font-weight: bold">{{val["count"]}}</td>
<td>{% if val['author'] in ('anton-d','unknown') %}{{val['author']}}{% else %}<a style="font-weight:bold;" href="/@{{val['author']}}"><img alt="@{{val['author']}}'s profile picture" loading="lazy" src="/@{{val['author']}}/pic" class="pp20">{{val['author']}}</a>{% endif %}</td>
<td style="font-weight: bold">{{marsey.name}}</td>
<td><img class="marsey" loading="lazy" data-bs-toggle="tooltip" alt=":{{marsey.name}}:" title=":{{marsey.name}}:" delay="0" src="/static/assets/images/emojis/{{marsey.name}}.webp?a=1007" ></td>
<td style="font-weight: bold">{{marsey.count}}</td>
<td><a style="color:#{{author.namecolor}};font-weight:bold" href="/@{{author.username}}"><img alt="@{{author.username}}'s profile picture" loading="lazy" src="{{author.profile_url}}" class="pp20"><span {% if author.patron %}class="patron" style="background-color:#{{author.namecolor}}"{% endif %}>{{author.username}}</span></a></td>
</tr>
{% endfor %}
</tbody>

File diff suppressed because one or more lines are too long