add form to submit banners and sidebar images

pull/225/head
Aevann 2024-03-05 01:54:15 +02:00
parent b781bbeced
commit 5b9dd4a8c4
19 changed files with 741 additions and 296 deletions

View File

@ -28,8 +28,10 @@ RUN mkdir /temp_songs
RUN mkdir /videos
RUN mkdir /audio
RUN mkdir /asset_submissions
RUN mkdir /asset_submissions/art
RUN mkdir /asset_submissions/emojis
RUN mkdir /asset_submissions/hats
RUN mkdir /asset_submissions/art/original
RUN mkdir /asset_submissions/emojis/original
RUN mkdir /asset_submissions/hats/original
RUN mkdir /var/log/rdrama

View File

@ -173,7 +173,7 @@
.fa-bell-slash:before{content:"\f1f6"}
.fa-chart-network:before{content:"\f78a"}
.fa-square-share-nodes:before{content:"\f1e1"}
.fa-sidebar:before{content:"\e24e"}
.fa-sidebar-flip:before{content:"\e24f"}
.fa-landscape:before{content:"\e1b5"}
.fa-external-link:before{content:"\f08e"}
.fa-circle-info:before{content:"\f05a"}
@ -3690,7 +3690,7 @@ ol > li::before {
}
.settings-nav > .nav-item > .nav-link.active, .settings-nav > .nav-item > .nav-link:focus, .settings-nav > .nav-item > .nav-link:hover {
color: var(--primary);
box-shadow: inset 0 -2px 0 var(--primary);
border-bottom: 3px solid var(--primary);
}
.post-nav .nav-link:hover, .post-nav .nav-link:focus, .settings-nav .nav-link:hover, .settings-nav .nav-link:focus {
color: var(--primary);
@ -3708,7 +3708,7 @@ ol > li::before {
}
.settings-nav > .nav-item > .nav-link.active {
color: var(--primary);
box-shadow: inset 0 -2px 0 var(--primary);
border-bottom: 3px solid var(--primary);
}
.settings-nav > .nav-item > .nav-link.active .fa, .settings-nav > .nav-item > .nav-link.active .fas, .settings-nav > .nav-item > .nav-link.active .far, .settings-nav > .nav-item > .nav-link.active .fab {
color: var(--primary);

View File

@ -0,0 +1,22 @@
function approve_art(t, id) {
postToast(t, `/admin/approve/art/${id}`,
{
"comment": document.getElementById(`${id}-comment`).value,
"author": document.getElementById(`${id}-author`).value,
},
() => {
document.getElementById(`${id}-art`).remove()
}
);
}
function remove_art(t, id) {
postToast(t, `/remove/art/${id}`,
{
"comment": document.getElementById(`${id}-comment`).value,
},
() => {
document.getElementById(`${id}-art`).remove()
}
);
}

View File

@ -0,0 +1,55 @@
import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.lazy import lazy
sidebar_hashes = {}
banner_hashes = {}
class ArtSubmission(Base):
__tablename__ = "art_submissions"
id = Column(Integer, primary_key=True)
kind = Column(String)
author_id = Column(Integer, ForeignKey("users.id"))
submitter_id = Column(Integer, ForeignKey("users.id"))
created_utc = Column(Integer)
approved = Column(Boolean, default=False)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id})>"
@property
@lazy
def badge_id(self):
return 99 if self.kind == "sidebar" else 101
@property
@lazy
def resize(self):
return 600 if self.kind == "sidebar" else 1600
@property
@lazy
def location_kind(self):
return 'sidebar' if self.kind == "sidebar" else 'banners'
@property
@lazy
def formatted_kind(self):
return 'sidebar image' if self.kind == "sidebar" else 'banner'
@property
@lazy
def msg_kind(self):
return 'Sidebar image' if self.kind == "sidebar" else 'Banner'
@property
@lazy
def hashes(self):
return sidebar_hashes if self.kind == "sidebar" else banner_hashes

View File

@ -246,6 +246,7 @@ FEATURES = {
'PATRON_ICONS': False,
'EMOJI_SUBMISSIONS': True,
'HAT_SUBMISSIONS': True,
'ART_SUBMISSIONS': True,
'NSFW_MARKING': True,
'PING_GROUPS': True,
'IP_LOGGING': False,
@ -589,16 +590,11 @@ LOTTERY_DURATION = 60 * 60 * 24 * 7
BUG_THREAD = 0
SIDEBAR_THREAD = 0
BANNER_THREAD = 0
BADGE_THREAD = 0
SNAPPY_THREAD = 0
CHANGELOG_THREAD = 0
POLL_THREAD = 0
ADMIGGER_THREADS = {SIDEBAR_THREAD, BANNER_THREAD, BADGE_THREAD, SNAPPY_THREAD, CHANGELOG_THREAD, POLL_THREAD}
SIDEBAR_REQUEST_THREAD = 0
BANNER_REQUEST_THREAD = 0
ADMIGGER_THREADS = {BADGE_THREAD, SNAPPY_THREAD, CHANGELOG_THREAD, POLL_THREAD}
MAX_IMAGE_SIZE_BANNER_RESIZED_MB = 2
MAX_IMAGE_AUDIO_SIZE_MB = 8
@ -654,16 +650,11 @@ if SITE in {'rdrama.net', 'staging.rdrama.net'}:
BUG_THREAD = 18459
SIDEBAR_THREAD = 37696
BANNER_THREAD = 37697
BADGE_THREAD = 37833
SNAPPY_THREAD = 37749
CHANGELOG_THREAD = 165657
POLL_THREAD = 79285
ADMIGGER_THREADS = {SIDEBAR_THREAD, BANNER_THREAD, BADGE_THREAD, SNAPPY_THREAD, CHANGELOG_THREAD, POLL_THREAD, 166300, 187078}
SIDEBAR_REQUEST_THREAD = 75878
BANNER_REQUEST_THREAD = 35835
ADMIGGER_THREADS = {BADGE_THREAD, SNAPPY_THREAD, CHANGELOG_THREAD, POLL_THREAD, 166300, 187078}
TRUESCORE_MINIMUM = 10
@ -767,16 +758,11 @@ elif SITE in {'watchpeopledie.tv', 'marsey.world'}:
BUG_THREAD = 61549
SIDEBAR_THREAD = 5403
BANNER_THREAD = 9869
BADGE_THREAD = 52519
SNAPPY_THREAD = 67186
CHANGELOG_THREAD = 56363
POLL_THREAD = 22937
ADMIGGER_THREADS = {SIDEBAR_THREAD, BANNER_THREAD, BADGE_THREAD, SNAPPY_THREAD, CHANGELOG_THREAD, POLL_THREAD, 106665}
SIDEBAR_REQUEST_THREAD = 29630
BANNER_REQUEST_THREAD = 29629
ADMIGGER_THREADS = {BADGE_THREAD, SNAPPY_THREAD, CHANGELOG_THREAD, POLL_THREAD, 106665}
MAX_VIDEO_SIZE_MB = 500
MAX_VIDEO_SIZE_MB_PATRON = 500

View File

@ -416,26 +416,46 @@ MODACTION_TYPES = {
"icon": 'fa-cat',
"color": 'bg-success'
},
'approve_hat': {
"str": 'approved hat',
"icon": 'fa-hat-cowboy',
'approve_sidebar': {
"str": 'approved a sidebar image made by {self.target_link}',
"icon": 'fa-sidebar-flip',
"color": 'bg-success'
},
'reject_hat': {
"str": 'rejected hat',
"icon": 'fa-hat-cowboy',
'reject_sidebar': {
"str": 'rejected a sidebar image made by {self.target_link}',
"icon": 'fa-sidebar-flip',
"color": 'bg-danger'
},
'approve_banner': {
"str": 'approved a banner made by {self.target_link}',
"icon": 'fa-landscape',
"color": 'bg-success'
},
'reject_banner': {
"str": 'rejected a banner made by {self.target_link}',
"icon": 'fa-landscape',
"color": 'bg-danger'
},
'approve_emoji': {
"str": 'approved emoji',
"str": 'approved an emoji made by {self.target_link}',
"icon": 'fa-cat',
"color": 'bg-success'
},
'reject_emoji': {
"str": 'rejected emoji',
"str": 'rejected an emoji made by {self.target_link}',
"icon": 'fa-cat',
"color": 'bg-danger'
},
'approve_hat': {
"str": 'approved hat made by {self.target_link}',
"icon": 'fa-hat-cowboy',
"color": 'bg-success'
},
'reject_hat': {
"str": 'rejected hat made by {self.target_link}',
"icon": 'fa-hat-cowboy',
"color": 'bg-danger'
},
'reset_password': {
"str": 'reset the password of {self.target_link}',
"icon": 'fa-lock',

View File

@ -7,7 +7,6 @@ import json
import requests
import ffmpeg
import gevent
import imagehash
from flask import abort, g, has_request_context, request
from mimetypes import guess_extension
from PIL import Image
@ -47,7 +46,7 @@ def media_ratelimit(v):
print(STARS, flush=True)
abort(500)
def process_files(files, v, body, is_dm=False, dm_user=None, admigger_thread=None, comment_body=None):
def process_files(files, v, body, is_dm=False, dm_user=None, is_badge_thread=False, comment_body=None):
if g.is_tor or not files.get("file"): return body
files = files.getlist('file')[:20]
@ -63,8 +62,8 @@ def process_files(files, v, body, is_dm=False, dm_user=None, admigger_thread=Non
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
url = process_image(name, v)
if admigger_thread:
process_admigger_entry(name, v, admigger_thread, comment_body)
if is_badge_thread:
process_badge_entry(name, v, comment_body)
elif file.content_type.startswith('video/'):
url = process_video(file, v)
elif file.content_type.startswith('audio/'):
@ -257,30 +256,6 @@ def process_image(filename, v, resize=0, trim=False, uploader_id=None):
abort(413, f"Max size for site assets is {MAX_IMAGE_SIZE_BANNER_RESIZED_MB} MB")
return None
if filename.startswith('files/assets/images/'):
path = filename.rsplit('/', 1)[0]
kind = path.split('/')[-1]
if kind in {'banners','sidebar'}:
hashes = {}
for img in os.listdir(path):
img_path = f'{path}/{img}'
if img_path == filename: continue
with Image.open(img_path) as i:
i_hash = str(imagehash.phash(i))
if i_hash not in hashes.keys():
hashes[i_hash] = img_path
with Image.open(filename) as i:
i_hash = str(imagehash.phash(i))
if i_hash in hashes.keys():
os.remove(filename)
return None
media = g.db.query(Media).filter_by(filename=filename, kind='image').one_or_none()
if media: g.db.delete(media)
@ -306,41 +281,27 @@ def send_file(filename):
rclone.copy(filename, 'no:/videos', ignore_existing=True, show_progress=False)
def process_sidebar_or_banner(oldname, v, type, resize):
li = sorted(os.listdir(f'files/assets/images/{SITE_NAME}/{type}'),
key=lambda e: int(e.split('.webp')[0]))[-1]
num = int(li.split('.webp')[0]) + 1
filename = f'files/assets/images/{SITE_NAME}/{type}/{num}.webp'
copyfile(oldname, filename)
process_image(filename, v, resize=resize)
def process_badge_entry(oldname, v, comment_body):
try:
json_body = '{' + comment_body.split('{')[1].split('}')[0] + '}'
badge_def = json.loads(json_body)
name = badge_def["name"]
def process_admigger_entry(oldname, v, admigger_thread, comment_body):
if admigger_thread == SIDEBAR_THREAD:
process_sidebar_or_banner(oldname, v, 'sidebar', 600)
elif admigger_thread == BANNER_THREAD:
banner_width = 1600
process_sidebar_or_banner(oldname, v, 'banners', banner_width)
elif admigger_thread == BADGE_THREAD:
try:
json_body = '{' + comment_body.split('{')[1].split('}')[0] + '}'
badge_def = json.loads(json_body)
name = badge_def["name"]
if len(name) > 50:
abort(400, "Badge name is too long (max 50 characters)")
if len(name) > 50:
abort(400, "Badge name is too long (max 50 characters)")
if not badge_name_regex.fullmatch(name):
abort(400, "Invalid badge name!")
if not badge_name_regex.fullmatch(name):
abort(400, "Invalid badge name!")
existing = g.db.query(BadgeDef).filter_by(name=name).one_or_none()
if existing: abort(409, "A badge with this name already exists!")
existing = g.db.query(BadgeDef).filter_by(name=name).one_or_none()
if existing: abort(409, "A badge with this name already exists!")
badge = BadgeDef(name=name, description=badge_def["description"])
g.db.add(badge)
g.db.flush()
filename = f'files/assets/images/{SITE_NAME}/badges/{badge.id}.webp'
copyfile(oldname, filename)
process_image(filename, v, resize=300, trim=True)
purge_files_in_cloudflare_cache(f"{SITE_FULL_IMAGES}/i/{SITE_NAME}/badges/{badge.id}.webp")
except Exception as e:
abort(400, str(e))
badge = BadgeDef(name=name, description=badge_def["description"])
g.db.add(badge)
g.db.flush()
filename = f'files/assets/images/{SITE_NAME}/badges/{badge.id}.webp'
copyfile(oldname, filename)
process_image(filename, v, resize=300, trim=True)
purge_files_in_cloudflare_cache(f"{SITE_FULL_IMAGES}/i/{SITE_NAME}/badges/{badge.id}.webp")
except Exception as e:
abort(400, str(e))

View File

@ -47,6 +47,8 @@ if FEATURES['HATS']:
from .hats import *
if FEATURES['EMOJI_SUBMISSIONS'] or FEATURES['HAT_SUBMISSIONS']:
from .asset_submissions import *
if FEATURES['ART_SUBMISSIONS']:
from .art_submissions import *
from .special import *
from .push_notifs import *
if FEATURES['PING_GROUPS']:

View File

@ -0,0 +1,190 @@
from shutil import copyfile, move
import imagehash
from files.classes.art_submissions import *
from files.helpers.config.const import *
from files.helpers.media import *
from files.helpers.useractions import badge_grant
from files.routes.wrappers import *
from files.__main__ import app, limiter
@app.get("/submit/sidebar")
@app.get("/submit/banners")
@feature_required('ART_SUBMISSIONS')
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def submit_art(v):
kind = request.path.split('/')[-1].rstrip('s')
entries = g.db.query(ArtSubmission).filter(
ArtSubmission.kind == kind,
ArtSubmission.approved == False,
).order_by(ArtSubmission.id.desc()).all()
for entry in entries:
entry.author = g.db.query(User.username).filter_by(id=entry.author_id).one()[0]
entry.submitter = g.db.query(User.username).filter_by(id=entry.submitter_id).one()[0]
return render_template("submit_art.html", v=v, entries=entries, kind=kind.title())
@app.post("/submit/art")
@feature_required('ART_SUBMISSIONS')
@limiter.limit('1/second', scope=rpath)
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
@limiter.limit("20/day", deduct_when=lambda response: response.status_code < 400)
@limiter.limit("20/day", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def submit_art_post(v):
if g.is_tor:
abort(400, "File uploads are not allowed through TOR!")
file = request.files["image"]
if not file or not file.content_type.startswith('image/'):
abort(400, "You need to submit an image!")
username = request.values.get('author', '').lower().strip()
author = get_user(username, v=v)
kind = request.values.get('kind', '').strip()
if kind not in {"sidebar", "banner"}:
abort(400, "Invalid kind!")
print(kind, flush=True)
entry = ArtSubmission(
kind=kind,
author_id=author.id,
submitter_id=v.id,
)
g.db.add(entry)
g.db.flush()
highquality = f'/asset_submissions/art/{entry.id}.webp'
file.save(highquality)
process_image(highquality, v) #to ensure not malware
path = f"files/assets/images/{SITE_NAME}/{entry.location_kind}"
if not entry.hashes:
for img in os.listdir(path):
img_path = f'{path}/{img}'
with Image.open(img_path) as i:
i_hash = str(imagehash.phash(i))
if i_hash not in entry.hashes.keys():
entry.hashes[i_hash] = img_path
with Image.open(highquality) as i:
i_hash = str(imagehash.phash(i))
if i_hash in entry.hashes.keys():
os.remove(highquality)
abort(400, f"Image already exists as a {entry.formatted_kind}!")
return {"message": f"{entry.msg_kind} submitted successfully!"}
@app.post("/admin/approve/art/<int:id>")
@feature_required('ART_SUBMISSIONS')
@limiter.limit('1/second', scope=rpath)
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@admin_level_required(PERMS['MODERATE_PENDING_SUBMITTED_ASSETS'])
def approve_art(v, id):
entry = g.db.get(ArtSubmission, id)
if not entry:
abort(404, "Art submission not found!")
old = f'/asset_submissions/art/{entry.id}.webp'
copyfile(old, f"/asset_submissions/art/original/{entry.id}.webp")
filename = f"files/assets/images/{SITE_NAME}/{entry.location_kind}/{entry.id}.webp"
move(old, filename)
process_image(filename, v, resize=entry.resize, trim=True)
entry_url = filename.replace('files', SITE_FULL_IMAGES)
author = request.values.get('author').strip()
author = get_user(author)
entry.author_id = author.id
g.db.add(entry)
badge_grant(author, entry.badge_id)
if v.id != author.id:
msg = f"@{v.username} (a site admin) has approved a {entry.formatted_kind} you made:\n{entry_url}"
comment = request.values.get("comment")
if comment:
msg += f"\nComment: `{comment}`"
send_repeatable_notification(author.id, msg)
if v.id != entry.submitter_id and author.id != entry.submitter_id:
msg = f"@{v.username} (a site admin) has approved a {entry.formatted_kind} you submitted:\n{entry_url}"
comment = request.values.get("comment")
if comment:
msg += f"\nComment: `{comment}`"
send_repeatable_notification(entry.submitter_id, msg)
note = entry_url
if comment:
note += f' - Comment: "{comment}"'
ma = ModAction(
kind=f"approve_{entry.kind}",
user_id=v.id,
target_user_id=entry.author_id,
_note=filter_emojis_only(note, link=True),
)
g.db.add(ma)
return {"message": f"{entry.msg_kind} approved!"}
@app.post("/remove/art/<int:id>")
@feature_required('ART_SUBMISSIONS')
@limiter.limit('1/second', scope=rpath)
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def remove_art(v, id):
entry = g.db.get(ArtSubmission, id)
if not entry:
abort(404, "Art submission not found!")
if v.id != entry.submitter_id and v.admin_level < PERMS['MODERATE_PENDING_SUBMITTED_ASSETS']:
abort(403)
if v.id != entry.submitter_id:
entry_url = f'{SITE_FULL_IMAGES}/asset_submissions/art/{entry.id}.webp'
msg = f"@{v.username} (a site admin) has rejected a {entry.formatted_kind} you submitted:\n{entry_url}"
comment = request.values.get("comment")
if comment:
msg += f"\nComment: `{comment}`"
send_repeatable_notification(entry.submitter_id, msg)
note = entry_url
if comment:
note += f' - Comment: "{comment}"'
ma = ModAction(
kind=f"reject_{entry.kind}",
user_id=v.id,
target_user_id=entry.author_id,
_note=filter_emojis_only(note, link=True),
)
g.db.add(ma)
os.remove(f'/asset_submissions/art/{entry.id}.webp')
msg = f"{entry.msg_kind} removed!"
g.db.delete(entry)
return {"message": msg}

View File

@ -248,10 +248,16 @@ def approve_emoji(v, name):
emoji.submitter_id = None
note = f':{emoji.name}:'
if comment:
note += f' - Comment: "{comment}"'
ma = ModAction(
kind="approve_emoji",
user_id=v.id,
_note=f'<img loading="lazy" data-bs-toggle="tooltip" alt=":{emoji.name}:" title=":{emoji.name}:" src="{SITE_FULL_IMAGES}/e/{emoji.name}.webp">'
target_user_id=emoji.author_id,
_note=filter_emojis_only(note, link=True),
)
g.db.add(ma)
@ -300,10 +306,15 @@ def remove_asset(cls, type_name, v, name):
send_repeatable_notification(asset.submitter_id, msg)
note = name
if comment:
note += f' - Comment: "{comment}"'
ma = ModAction(
kind=f"reject_{type_name}",
user_id=v.id,
_note=name
target_user_id=asset.author_id,
_note=filter_emojis_only(note, link=True),
)
g.db.add(ma)
@ -464,10 +475,15 @@ def approve_hat(v, name):
new_path = f'/asset_submissions/hats/original/{hat.name}.{i.format.lower()}'
rename(highquality, new_path)
note = f'[{hat.name}]({SITE_FULL_IMAGES}/i/hats/{hat.name}.webp)'
if comment:
note += f' - Comment: "{comment}"'
ma = ModAction(
kind="approve_hat",
user_id=v.id,
_note=f'<a href="{SITE_FULL_IMAGES}/i/hats/{hat.name}.webp">{hat.name}</a>'
target_user_id=hat.author_id,
_note=filter_emojis_only(note, link=True),
)
g.db.add(ma)

View File

@ -181,14 +181,14 @@ def comment(v):
if parent_user.has_blocked(v) or parent_user.has_muted(v):
notify_op = False
if posting_to_post and v.admin_level >= PERMS['USE_ADMIGGER_THREADS'] and post_target.id in {SIDEBAR_THREAD, BANNER_THREAD, BADGE_THREAD}:
admigger_thread = post_target.id
if posting_to_post and v.admin_level >= PERMS['USE_ADMIGGER_THREADS'] and post_target.id == BADGE_THREAD:
is_badge_thread = True
comment_body = body
else:
admigger_thread = None
is_badge_thread = False
comment_body = None
body = process_files(request.files, v, body, admigger_thread=admigger_thread, comment_body=comment_body)
body = process_files(request.files, v, body, is_badge_thread=is_badge_thread, comment_body=comment_body)
if len(body) > COMMENT_BODY_LENGTH_LIMIT:
abort(400, f'Comment body is too long (max {COMMENT_BODY_LENGTH_LIMIT} characters)')

View File

@ -164,8 +164,7 @@ def inject_constants():
"DEFAULT_THEME":DEFAULT_THEME, "DESCRIPTION":DESCRIPTION,
"has_sidebar":has_sidebar, "has_logo":has_logo,
"patron":patron, "get_setting": get_setting,
"SIDEBAR_THREAD":SIDEBAR_THREAD, "BANNER_THREAD":BANNER_THREAD, "BUG_THREAD":BUG_THREAD,
"BADGE_THREAD":BADGE_THREAD, "SNAPPY_THREAD":SNAPPY_THREAD, "CHANGELOG_THREAD":CHANGELOG_THREAD,
"BUG_THREAD":BUG_THREAD, "BADGE_THREAD":BADGE_THREAD, "SNAPPY_THREAD":SNAPPY_THREAD, "CHANGELOG_THREAD":CHANGELOG_THREAD,
"approved_embed_hosts":approved_embed_hosts, "POST_BODY_LENGTH_LIMIT":POST_BODY_LENGTH_LIMIT,
"SITE_SETTINGS":get_settings(), "EMAIL":EMAIL, "max": max, "min": min, "can_see":can_see,
"TELEGRAM_ID":TELEGRAM_ID, "TWITTER_ID":TWITTER_ID, "TRUESCORE_MINIMUM":TRUESCORE_MINIMUM, "TRUESCORE_DONATE_MINIMUM":TRUESCORE_DONATE_MINIMUM,
@ -185,6 +184,6 @@ def inject_constants():
"MAX_VIDEO_SIZE_MB":MAX_VIDEO_SIZE_MB, "MAX_VIDEO_SIZE_MB_PATRON":MAX_VIDEO_SIZE_MB_PATRON,
"CURSORMARSEY_DEFAULT":CURSORMARSEY_DEFAULT, "SNAPPY_ID":SNAPPY_ID, "ZOZBOT_ID":ZOZBOT_ID, "get_running_orgy":get_running_orgy,
"bar_position":bar_position, "datetime":datetime, "CSS_LENGTH_LIMIT":CSS_LENGTH_LIMIT, "cache":cache, "emoji_count":emoji_count, "HOLE_SIDEBAR_COLUMN_LENGTH":HOLE_SIDEBAR_COLUMN_LENGTH, "HOLE_SNAPPY_QUOTES_LENGTH":HOLE_SNAPPY_QUOTES_LENGTH,
"SIDEBAR_REQUEST_THREAD":SIDEBAR_REQUEST_THREAD, "BANNER_REQUEST_THREAD":BANNER_REQUEST_THREAD, "top_poster_of_the_day":top_poster_of_the_day,
"top_poster_of_the_day":top_poster_of_the_day,
}

View File

@ -18,16 +18,10 @@
</ul>
{%- endif %}
{% if v.admin_level >= PERMS['USE_ADMIGGER_THREADS'] and (SITE_NAME == 'rDrama' or SIDEBAR_THREAD or BANNER_THREAD or BADGE_THREAD or SNAPPY_THREAD) %}
{% if v.admin_level >= PERMS['USE_ADMIGGER_THREADS'] and (SITE_NAME == 'rDrama' or BADGE_THREAD or SNAPPY_THREAD) %}
<h4>Add Stuff</h4>
<ul>
{% if v.admin_level >= PERMS['USE_ADMIGGER_THREADS'] %}
{% if SIDEBAR_THREAD %}
<li><a href="/post/{{SIDEBAR_THREAD}}">Add Sidebar Images</a></li>
{% endif %}
{% if BANNER_THREAD %}
<li><a href="/post/{{BANNER_THREAD}}">Add Banners</a></li>
{% endif %}
{% if BADGE_THREAD %}
<li><a href="/post/{{BADGE_THREAD}}">Add Badges</a></li>
{% endif %}
@ -37,6 +31,10 @@
{% endif %}
{% if v.admin_level >= PERMS['MODERATE_PENDING_SUBMITTED_ASSETS'] %}
{% if FEATURES['ART_SUBMISSIONS'] -%}
<li><a href="/submit/sidebar">Approve or Reject Sidebar Images</a></li>
<li><a href="/submit/banners">Approve or Reject Banners</a></li>
{% endif %}
{% if FEATURES['EMOJI_SUBMISSIONS'] -%}
<li><a href="/submit/emojis">Approve or Reject Emojis</a></li>
{% endif %}

View File

@ -3,103 +3,98 @@
{% block pagetype %}directory{% endblock %}
{%- set DIRECTORY = [
(
'Donate',
'',
'fa-dollar-sign', 'green',
'/donate',
),
(
'Bugs / Suggestions',
'Something broken? Improvements?',
'fa-bug', '#6b8e23',
'/post/' ~ BUG_THREAD,
),
(
'Changelog',
'Log of all site changes.',
'fa-clipboard', '#ffffff',
'/post/' ~ CHANGELOG_THREAD,
),
(
'Submit Emojis',
'Submit new emojis for the site.',
'fa-smile-beam', '#fec83c',
'/submit/emojis',
),
(
'Submit Sidebar Art',
'Original ' ~ SITE_NAME ~ '-themed works.',
'fa-sidebar', '#f5fffa',
'/post/' ~ SIDEBAR_REQUEST_THREAD,
),
(
'Submit Banner Art',
'Original ' ~ SITE_NAME ~ '-themed works.',
'fa-landscape', '#87cefa',
'/post/' ~ BANNER_REQUEST_THREAD,
),
(
'View All Emojis',
'',
'fa-smile-beam', '#fec83c',
'/emojis/marsey',
),
(
'View All Sidebar Pictures',
'',
'fa-sidebar', '#f5fffa',
'/post/' ~ SIDEBAR_THREAD,
),
(
'View All Banner Pictures',
'',
'fa-landscape', '#87cefa',
'/post/' ~ BANNER_THREAD,
),
(
'View All Snappy Quotes',
'',
'fa-robot', '#adff2f',
'/post/' ~ SNAPPY_THREAD,
),
] -%}
{%- if SITE_NAME == 'rDrama' -%}
{%- do DIRECTORY.extend([
(
'Submit Hats',
'Submit a Hat to be added.',
'fa-hat-cowboy', '#7c603e',
'/submit/hats',
),
(
'Update Emojis or Hats',
'Ask for an emoji or a hat to be updated with a better version or for emoji tags to be changed.',
'fa-smile-beam', '#fec83c',
'/post/103085',
),
(
'Submit Snappy Quotes',
'Sentient.',
'fa-robot', '#adff2f',
'/post/33652',
),
(
'Emoji Commissions',
'Request an Emoji to be made.',
'fa-coins', '#ffd700',
'/post/37677',
),
(
'Reportmaxxxing Bounties',
'Request a redditor be banned.',
'fa-gavel', '#dc3545',
'/post/215970',
),
])-%}
{%- endif -%}
(
'Donate',
'',
'fa-dollar-sign', 'green',
'/donate',
True,
),
(
'Bugs / Suggestions',
'Something broken? Improvements?',
'fa-bug', '#6b8e23',
'/post/' ~ BUG_THREAD,
BUG_THREAD,
),
(
'Changelog',
'Log of all site changes.',
'fa-clipboard', '#ffffff',
'/post/' ~ CHANGELOG_THREAD,
CHANGELOG_THREAD,
),
(
'Submit Sidebar Art',
'Original ' ~ SITE_NAME ~ '-themed works.',
'fa-sidebar-flip', '#f5fffa',
'/submit/sidebar',
FEATURES['ART_SUBMISSIONS'],
),
(
'Submit Banner Art',
'Original ' ~ SITE_NAME ~ '-themed works.',
'fa-landscape', '#87cefa',
'/submit/banners',
FEATURES['ART_SUBMISSIONS'],
),
(
'Submit Emojis',
'Submit new emojis for the site.',
'fa-smile-beam', '#fec83c',
'/submit/emojis',
FEATURES['EMOJI_SUBMISSIONS'],
),
(
'Submit Hats',
'Submit a Hat to be added.',
'fa-hat-cowboy', '#7c603e',
'/submit/hats',
FEATURES['HAT_SUBMISSIONS'],
),
(
'Submit Snappy Quotes',
'Sentient.',
'fa-robot', '#adff2f',
'/post/33652',
SITE == 'rdrama.net',
),
(
'Update Emojis or Hats',
'Ask for an emoji or a hat to be updated with a better version or for emoji tags to be changed.',
'fa-smile-beam', '#fec83c',
'/post/103085',
SITE == 'rdrama.net',
),
(
'View All Emojis',
'',
'fa-smile-beam', '#fec83c',
'/emojis/marsey',
True,
),
(
'View All Snappy Quotes',
'',
'fa-robot', '#adff2f',
'/post/' ~ SNAPPY_THREAD,
SNAPPY_THREAD,
),
(
'Emoji Commissions',
'Request an Emoji to be made.',
'fa-coins', '#ffd700',
'/post/37677',
SITE == 'rdrama.net',
),
(
'Reportmaxxxing Bounties',
'Request a redditor be banned.',
'fa-gavel', '#dc3545',
'/post/215970',
SITE == 'rdrama.net',
),
] -%}
{% block content %}
<div id="directory--wrapper">

View File

@ -0,0 +1,73 @@
{% extends "submit_navbar.html" %}
{% block pagetitle %}Submit {{kind}}{% if kind == 'Sidebar' %} Images{% else %}s{% endif %}{% endblock %}
{% block pagetype %}message{% endblock %}
{% block content %}
<h2 class="mt-3 mt-md-5">Submit {{kind}} {% if kind == 'Sidebar' %}Image{% endif %}</h2>
<div class="settings-section rounded">
<div class="d-lg-flex">
<div class="body w-lg-100">
<form action="/submit/art" method="post" enctype="multipart/form-data" data-nonce="{{g.nonce}}" data-onsubmit="sendFormXHRReload(this)">
<input hidden name="formkey" value="{{v|formkey}}" class="notranslate" translate="no">
<input hidden name="kind" value="{{kind.lower()}}" class="notranslate" translate="no">
<div id="image-upload-block">
<div><label class="mt-3">Image</label></div>
<img loading="lazy" id="image-preview" class="d-none" style="max-width:50%;border:5px white solid">
<label class="btn btn-secondary m-0" for="file-upload">
<div>Select Image</div>
<input autocomplete="off" id="file-upload" accept="image/*" type="file" name="image" {% if g.is_tor %}disabled{% endif %} hidden>
</label>
</div>
<label class="mt-3" for="author">Author</label>
<input autocomplete="off" type="text" id="author" class="form-control" name="author" maxlength="30" pattern='[a-zA-Z0-9_\-]{1,30}|\?{3}' placeholder="Required" value="{{username}}" required>
<div class="footer mt-5">
<div class="d-flex">
<input id="submit-btn" disabled type="submit" class="btn btn-primary ml-auto" value="Submit {{kind}}">
</div>
</div>
</form>
</div>
</div>
</div>
<h2 class="mt-5 mx-1">Pending Approval</h2>
<div class="mt-5">
<div class="col px-0">
<div class="settings">
{% for entry in entries %}
<div id="{{entry.id}}-art" class="settings-section rounded">
<div class="d-lg-flex">
<div class="body w-lg-100">
<input hidden value="{{v|formkey}}" class="notranslate" translate="no">
<div><label class="mt-3">Image</label></div>
<img loading="lazy" src="{{SITE_FULL_IMAGES}}/asset_submissions/art/{{entry.id}}.webp?s={{range(1, 10000000)|random}}" style="max-width:50%;border:5px white solid">
<div><label class="mt-3" for="{{entry.id}}-submitter">Submitter</label></div>
<input autocomplete="off" type="text" id="{{entry.id}}-submitter" class="form-control" maxlength="30" value="{{entry.submitter}}" readonly>
<label class="mt-3" for="{{entry.id}}-author">Author</label>
<input autocomplete="off" type="text" id="{{entry.id}}-author" class="form-control" maxlength="30" value="{{entry.author}}" pattern='[a-zA-Z0-9_\-]{1,30}|\?{3}' placeholder="Required" required {% if v.admin_level < PERMS['MODERATE_PENDING_SUBMITTED_ASSETS'] %}readonly{% endif %}>
</div>
</div>
{% if v.admin_level >= PERMS['MODERATE_PENDING_SUBMITTED_ASSETS'] or v.id == entry.submitter_id %}
<div class="d-flex my-4 mx-3">
<input autocomplete="off" type="text" id="{{entry.id}}-comment" class="form-control mr-4" placeholder="Comment..." maxlength="500" {% if v.admin_level < PERMS['MODERATE_PENDING_SUBMITTED_ASSETS'] %}hidden{% endif %}>
<button type="button" class="btn btn-danger ml-auto" data-nonce="{{g.nonce}}" data-onclick="remove_art(this, '{{entry.id}}')">Remove</button>
{% if v.admin_level >= PERMS['MODERATE_PENDING_SUBMITTED_ASSETS'] %}
<button type="button" class="btn btn-success ml-3 mr-1" data-nonce="{{g.nonce}}" data-onclick="approve_art(this, '{{entry.id}}')">Approve</button>
{% endif %}
</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</div>
<script defer src="{{'js/submit_art.js' | asset}}"></script>
{% endblock %}

View File

@ -1,68 +1,66 @@
{% extends "default.html" %}
{% extends "submit_navbar.html" %}
{% block pagetitle %}Submit Emojis{% endblock %}
{% block pagetype %}message{% endblock %}
{% block content %}
<div class="mx-4">
<h2 class="mt-5">Submit Emoji</h2>
<div class="settings-section rounded">
<div class="d-lg-flex">
<div class="body w-lg-100">
<form action="/submit/emojis" method="post" enctype="multipart/form-data" data-nonce="{{g.nonce}}" data-onsubmit="sendFormXHR(this)">
<input hidden name="formkey" value="{{v|formkey}}" class="notranslate" translate="no">
<h2 class="mt-3 mt-md-5">Submit Emoji</h2>
<div class="settings-section rounded">
<div class="d-lg-flex">
<div class="body w-lg-100">
<form action="/submit/emojis" method="post" enctype="multipart/form-data" data-nonce="{{g.nonce}}" data-onsubmit="sendFormXHR(this)">
<input hidden name="formkey" value="{{v|formkey}}" class="notranslate" translate="no">
<div id="image-upload-block">
<div><label class="mt-3">Image</label></div>
<div id="image-upload-block">
<div><label class="mt-3">Image</label></div>
<img loading="lazy" id="image-preview" class="d-none" style="max-width:50%;border:5px white solid">
<label class="btn btn-secondary m-0" for="file-upload">
<div>Select Image</div>
<input autocomplete="off" id="file-upload" accept="image/*" type="file" name="image" {% if g.is_tor %}disabled{% endif %} hidden>
</label>
<img loading="lazy" id="image-preview" class="d-none" style="max-width:50%;border:5px white solid">
<label class="btn btn-secondary m-0" for="file-upload">
<div>Select Image</div>
<input autocomplete="off" id="file-upload" accept="image/*" type="file" name="image" {% if g.is_tor %}disabled{% endif %} hidden>
</label>
</div>
<label class="mt-3" for="kind">Kind</label>
<div class="input-group">
<select autocomplete="off" id='kind' class="form-control" name="kind" required>
{% if not kind %}
<option hidden disabled selected value>-- select an option --</option>
{% endif %}
{% for entry in EMOJI_KINDS %}
<option value="{{entry}}" {% if entry == kind %}selected{% endif %}>
{{entry}}
</option>
{% endfor %}
</select>
</div>
<label class="mt-3" for="name">Emoji Name</label>
<input autocomplete="off" type="text" id="name" class="form-control" name="name" maxlength="30" pattern='[a-zA-Z0-9]{1,30}' placeholder="Required" value="{{name}}" required>
<label class="mt-3" for="author">Author</label>
<input autocomplete="off" type="text" id="author" class="form-control" name="author" maxlength="30" pattern='[a-zA-Z0-9_\-]{1,30}|\?{3}' placeholder="Required" value="{{username}}" required>
<label class="mt-3" for="tags">Tags (must be separated by spaces)</label>
<input autocomplete="off" type="text" id="tags" class="form-control" name="tags" maxlength="200" pattern='[a-zA-Z0-9: ]{1,200}' placeholder="Required" value="{{tags}}" required>
{% if FEATURES['NSFW_MARKING'] %}
<div class="custom-control custom-checkbox mt-4 pt-1 ml-1">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="emoji-nsfw" name="nsfw">
<label class="custom-control-label" for="emoji-nsfw">NSFW</label>
</div>
{% endif %}
<label class="mt-3" for="kind">Kind</label>
<div class="input-group">
<select autocomplete="off" id='kind' class="form-control" name="kind" required>
{% if not kind %}
<option hidden disabled selected value>-- select an option --</option>
{% endif %}
{% for entry in EMOJI_KINDS %}
<option value="{{entry}}" {% if entry == kind %}selected{% endif %}>
{{entry}}
</option>
{% endfor %}
</select>
<div class="footer mt-5">
<div class="d-flex">
<input id="submit-btn" disabled type="submit" class="btn btn-primary ml-auto" value="Submit Emoji">
</div>
<label class="mt-3" for="name">Emoji Name</label>
<input autocomplete="off" type="text" id="name" class="form-control" name="name" maxlength="30" pattern='[a-zA-Z0-9]{1,30}' placeholder="Required" value="{{name}}" required>
<label class="mt-3" for="author">Author</label>
<input autocomplete="off" type="text" id="author" class="form-control" name="author" maxlength="30" pattern='[a-zA-Z0-9_\-]{1,30}|\?{3}' placeholder="Required" value="{{username}}" required>
<label class="mt-3" for="tags">Tags (must be separated by spaces)</label>
<input autocomplete="off" type="text" id="tags" class="form-control" name="tags" maxlength="200" pattern='[a-zA-Z0-9: ]{1,200}' placeholder="Required" value="{{tags}}" required>
{% if FEATURES['NSFW_MARKING'] %}
<div class="custom-control custom-checkbox mt-4 pt-1 ml-1">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="emoji-nsfw" name="nsfw">
<label class="custom-control-label" for="emoji-nsfw">NSFW</label>
</div>
{% endif %}
<div class="footer mt-5">
<div class="d-flex">
<input id="submit-btn" disabled type="submit" class="btn btn-primary ml-auto" value="Submit Emoji">
</div>
</div>
</form>
</div>
</div>
</form>
</div>
</div>
</div>
<h2 class="mt-5 mx-4">Pending Approval</h2>
<div class="row mt-5 mx-4">
<h2 class="mt-5 mx-1">Pending Approval</h2>
<div class="mt-5">
<div class="col px-0">
<div class="settings">
{% for emoji in emojis %}

View File

@ -1,51 +1,49 @@
{% extends "default.html" %}
{% extends "submit_navbar.html" %}
{% block pagetitle %}Submit Hats{% endblock %}
{% block pagetype %}message{% endblock %}
{% block content %}
<div class="mx-4">
<h2 class="mt-5">Submit Hat</h2>
<div class="settings-section rounded">
<div class="d-lg-flex">
<div class="body w-lg-100">
<form action="/submit/hats" method="post" enctype="multipart/form-data" data-nonce="{{g.nonce}}" data-onsubmit="sendFormXHRReload(this)">
<input hidden name="formkey" value="{{v|formkey}}" class="notranslate" translate="no">
<h2 class="mt-3 mt-md-5">Submit Hat</h2>
<div class="settings-section rounded">
<div class="d-lg-flex">
<div class="body w-lg-100">
<form action="/submit/hats" method="post" enctype="multipart/form-data" data-nonce="{{g.nonce}}" data-onsubmit="sendFormXHRReload(this)">
<input hidden name="formkey" value="{{v|formkey}}" class="notranslate" translate="no">
<div id="image-upload-block">
<div><label class="mt-3">Image</label></div>
<div id="image-upload-block">
<div><label class="mt-3">Image</label></div>
<img loading="lazy" id="image-preview" class="d-none" style="max-width:50%;border:5px white solid">
<label class="btn btn-secondary m-0" for="file-upload">
<div>Select Image</div>
<input autocomplete="off" id="file-upload" accept="image/*" type="file" name="image" {% if g.is_tor %}disabled{% endif %} hidden>
</label>
<img loading="lazy" id="image-preview" class="d-none" style="max-width:50%;border:5px white solid">
<label class="btn btn-secondary m-0" for="file-upload">
<div>Select Image</div>
<input autocomplete="off" id="file-upload" accept="image/*" type="file" name="image" {% if g.is_tor %}disabled{% endif %} hidden>
</label>
</div>
<div id="hat-design-reference-block" class="mt-3">
<a href="{{SITE_FULL_IMAGES}}/i/hat-template.png" class="font-weight-bold">Hat Template</a> &mdash; 100x130px (do not resize), circle is profile picture, do not include circle in final submission.
</div>
<label class="mt-3" for="name">Hat Name</label>
<input autocomplete="off" type="text" id="name" class="form-control" name="name" maxlength="50" placeholder="Required" value="{{name}}" required>
<label class="mt-3" for="author">Author</label>
<input autocomplete="off" type="text" id="author" class="form-control" name="author" maxlength="30" pattern='[a-zA-Z0-9_\-]{1,30}' placeholder="Required" value="{{username}}" required>
<label class="mt-3" for="description">Description</label>
<input autocomplete="off" type="text" id="description" class="form-control" name="description" maxlength="300" pattern='[^<>&\n\t]{1,300}' placeholder="Required" value="{{description}}" required>
<div class="footer mt-5">
<div class="d-flex">
<input id="submit-btn" disabled type="submit" class="btn btn-primary ml-auto" value="Submit Hat">
</div>
<div id="hat-design-reference-block" class="mt-3">
<a href="{{SITE_FULL_IMAGES}}/i/hat-template.png" class="font-weight-bold">Hat Template</a> &mdash; 100x130px (do not resize), circle is profile picture, do not include circle in final submission.
</div>
<label class="mt-3" for="name">Hat Name</label>
<input autocomplete="off" type="text" id="name" class="form-control" name="name" maxlength="50" placeholder="Required" value="{{name}}" required>
<label class="mt-3" for="author">Author</label>
<input autocomplete="off" type="text" id="author" class="form-control" name="author" maxlength="30" pattern='[a-zA-Z0-9_\-]{1,30}' placeholder="Required" value="{{username}}" required>
<label class="mt-3" for="description">Description</label>
<input autocomplete="off" type="text" id="description" class="form-control" name="description" maxlength="300" pattern='[^<>&\n\t]{1,300}' placeholder="Required" value="{{description}}" required>
<div class="footer mt-5">
<div class="d-flex">
<input id="submit-btn" disabled type="submit" class="btn btn-primary ml-auto" value="Submit Hat">
</div>
</div>
</form>
</div>
</div>
</form>
</div>
</div>
</div>
<h2 class="mt-5 mx-4">Pending Approval</h2>
<div class="row mt-5 mx-4">
<h2 class="mt-5 mx-1">Pending Approval</h2>
<div class="mt-5">
<div class="col px-0">
<div class="settings">
{% for hat in hats %}

View File

@ -0,0 +1,95 @@
{%- extends 'root.html' -%}
{% block pagetitle %}User List{% endblock %}
{% block pagetype %}meta_navbar{% endblock %}
{% block body_attributes %}class="has_header"{% endblock %}
{% block body %}
{% include "header.html" %}
{% block subNav %}
<div class="container-fluid bg-white sticky ml-2 d-mob-none" style="padding-top: 50px; padding-bottom: 0 !important">
<div class="row box-shadow-bottom">
<div class="col">
<div class="container" style="padding-bottom: 0">
<div class="row box-shadow-bottom bg-white">
<div class="col">
<div class="d-flex flex-row-reverse justify-content-end">
<ul class="nav settings-nav" style="margin-left: -15px">
{% if FEATURES['ART_SUBMISSIONS'] %}
<li class="nav-item">
<a class="nav-link{% if request.path == '/submit/sidebar' %} active{% endif %}" href="/submit/sidebar">
<i class="fas fa-sidebar-flip pr-2"></i>Sidebar Images
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if request.path == '/submit/banners' %} active{% endif %}" href="/submit/banners">
<i class="fas fa-landscape pr-2"></i>Banners
</a>
</li>
{% endif %}
{% if FEATURES['EMOJI_SUBMISSIONS'] %}
<li class="nav-item">
<a class="nav-link{% if request.path == '/submit/emojis' %} active{% endif %}" href="/submit/emojis">
<i class="fas fa-smile-beam pr-2"></i>Emojis
</a>
</li>
{% endif %}
{% if FEATURES['HAT_SUBMISSIONS'] %}
<li class="nav-item">
<a class="nav-link{% if request.path == '/submit/hats' %} active{% endif %}" href="/submit/hats">
<i class="fas fa-hat-cowboy pr-2"></i>Hats
</a>
</li>
{% endif %}
{% if SITE == 'rdrama.net' %}
<li class="nav-item">
<a class="nav-link" href="/post/33652">
<i class="fas fa-robot pr-2"></i>Snappy Quotes
</a>
</li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid bg-white sticky d-md-none" style="padding-top: 20px; padding-bottom: 0">
<div class="row box-shadow-bottom">
<div class="col px-0">
<div class="d-flex flex-row-reverse justify-content-center">
<ul class="nav settings-nav">
{% if FEATURES['ART_SUBMISSIONS'] %}
<li class="nav-item">
<a style="padding: 0.75rem 1.5rem" class="nav-link{% if request.path == '/submit/sidebar' %} active{% endif %}" href="/submit/sidebar"><i class="fas fa-sidebar-flip text-lg mr-0"></i></a>
</li>
<li class="nav-item">
<a style="padding: 0.75rem 1.5rem" class="nav-link{% if request.path == '/submit/banners' %} active{% endif %}" href="/submit/banners"><i class="fas fa-landscape text-lg mr-0"></i></a>
</li>
{% endif %}
{% if FEATURES['EMOJI_SUBMISSIONS'] %}
<li class="nav-item">
<a style="padding: 0.75rem 1.5rem" class="nav-link{% if request.path == '/submit/emojis' %} active{% endif %}" href="/submit/emojis"><i class="fas fa-smile-beam text-lg mr-0"></i></a>
</li>
{% endif %}
{% if FEATURES['HAT_SUBMISSIONS'] %}
<li class="nav-item">
<a style="padding: 0.75rem 1.5rem" class="nav-link{% if request.path == '/submit/hats' %} active{% endif %}" href="/submit/hats"><i class="fas fa-hat-cowboy text-lg mr-0"></i></a>
</li>
{% endif %}
{% if SITE == 'rdrama.net' %}
<li class="nav-item">
<a style="padding: 0.75rem 1.5rem" class="nav-link{% if request.path == '/post/33652' %} active{% endif %}" href="/post/33652"><i class="fas fa-robot text-lg mr-0"></i></a>
</li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
<div class="pt-3 container">
{% block content %}{% endblock %}
</div>
{% endblock %}

View File

@ -0,0 +1,35 @@
create table art_submissions (
id integer primary key,
kind varchar(7) not null,
author_id integer not null,
submitter_id integer not null,
created_utc integer not null,
approved bool not null
);
CREATE SEQUENCE public.art_submissions_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.art_submissions_id_seq OWNED BY public.art_submissions.id;
ALTER TABLE ONLY public.art_submissions ALTER COLUMN id SET DEFAULT nextval('public.art_submissions_id_seq'::regclass);
rdrama:
SELECT pg_catalog.setval('public.art_submissions_id_seq', 1477, true);
wpd:
SELECT pg_catalog.setval('public.art_submissions_id_seq', 157, true);
alter table only art_submissions
add constraint art_submissions_author_fkey foreign key (author_id) references public.users(id);
alter table only art_submissions
add constraint art_submissions_submitter_fkey foreign key (submitter_id) references public.users(id);
delete from modactions where kind in ('approve_emoji', 'reject_emoji', 'approve_hat', 'reject_hat');