From 5b9dd4a8c4d1c8de481a3935f13bad7d37165769 Mon Sep 17 00:00:00 2001 From: Aevann Date: Tue, 5 Mar 2024 01:54:15 +0200 Subject: [PATCH] add form to submit banners and sidebar images --- Dockerfile | 2 + files/assets/css/main.css | 6 +- files/assets/js/submit_art.js | 22 ++ files/classes/art_submissions.py | 55 +++++ files/helpers/config/const.py | 22 +- files/helpers/config/modaction_types.py | 36 +++- files/helpers/media.py | 85 +++----- files/routes/__init__.py | 2 + files/routes/art_submissions.py | 190 ++++++++++++++++++ files/routes/asset_submissions.py | 22 +- files/routes/comments.py | 8 +- files/routes/jinja2.py | 5 +- files/templates/admin/admin_home.html | 12 +- files/templates/directory.html | 189 +++++++++-------- files/templates/submit_art.html | 73 +++++++ files/templates/submit_emojis.html | 104 +++++----- files/templates/submit_hats.html | 74 ++++--- files/templates/submit_navbar.html | 95 +++++++++ .../20240304-add-art-submission-flow.sql | 35 ++++ 19 files changed, 741 insertions(+), 296 deletions(-) create mode 100644 files/assets/js/submit_art.js create mode 100644 files/classes/art_submissions.py create mode 100644 files/routes/art_submissions.py create mode 100644 files/templates/submit_art.html create mode 100644 files/templates/submit_navbar.html create mode 100644 migrations/20240304-add-art-submission-flow.sql diff --git a/Dockerfile b/Dockerfile index a25d9b551..b84785b3c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/files/assets/css/main.css b/files/assets/css/main.css index e4aa72a10..2c3485c8f 100644 --- a/files/assets/css/main.css +++ b/files/assets/css/main.css @@ -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); diff --git a/files/assets/js/submit_art.js b/files/assets/js/submit_art.js new file mode 100644 index 000000000..e52551682 --- /dev/null +++ b/files/assets/js/submit_art.js @@ -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() + } + ); +} diff --git a/files/classes/art_submissions.py b/files/classes/art_submissions.py new file mode 100644 index 000000000..8e67f4e54 --- /dev/null +++ b/files/classes/art_submissions.py @@ -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 diff --git a/files/helpers/config/const.py b/files/helpers/config/const.py index bf683545d..829f33d18 100644 --- a/files/helpers/config/const.py +++ b/files/helpers/config/const.py @@ -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 diff --git a/files/helpers/config/modaction_types.py b/files/helpers/config/modaction_types.py index a9908955c..6f1d9fc23 100644 --- a/files/helpers/config/modaction_types.py +++ b/files/helpers/config/modaction_types.py @@ -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', diff --git a/files/helpers/media.py b/files/helpers/media.py index 142a7efe6..0fb2602ed 100644 --- a/files/helpers/media.py +++ b/files/helpers/media.py @@ -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)) diff --git a/files/routes/__init__.py b/files/routes/__init__.py index e2e43b170..d917cbf53 100644 --- a/files/routes/__init__.py +++ b/files/routes/__init__.py @@ -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']: diff --git a/files/routes/art_submissions.py b/files/routes/art_submissions.py new file mode 100644 index 000000000..98b921a4a --- /dev/null +++ b/files/routes/art_submissions.py @@ -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/") +@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/") +@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} diff --git a/files/routes/asset_submissions.py b/files/routes/asset_submissions.py index c87d1fa98..150bc624c 100644 --- a/files/routes/asset_submissions.py +++ b/files/routes/asset_submissions.py @@ -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':{emoji.name}:' + 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'{hat.name}' + target_user_id=hat.author_id, + _note=filter_emojis_only(note, link=True), ) g.db.add(ma) diff --git a/files/routes/comments.py b/files/routes/comments.py index 7f3171b5f..d073882a7 100644 --- a/files/routes/comments.py +++ b/files/routes/comments.py @@ -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)') diff --git a/files/routes/jinja2.py b/files/routes/jinja2.py index 72984c1cb..d2b8717be 100644 --- a/files/routes/jinja2.py +++ b/files/routes/jinja2.py @@ -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, } diff --git a/files/templates/admin/admin_home.html b/files/templates/admin/admin_home.html index d35719775..cad5f0783 100644 --- a/files/templates/admin/admin_home.html +++ b/files/templates/admin/admin_home.html @@ -18,16 +18,10 @@ {%- 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) %}

Add Stuff

    {% if v.admin_level >= PERMS['USE_ADMIGGER_THREADS'] %} - {% if SIDEBAR_THREAD %} -
  • Add Sidebar Images
  • - {% endif %} - {% if BANNER_THREAD %} -
  • Add Banners
  • - {% endif %} {% if BADGE_THREAD %}
  • Add Badges
  • {% endif %} @@ -37,6 +31,10 @@ {% endif %} {% if v.admin_level >= PERMS['MODERATE_PENDING_SUBMITTED_ASSETS'] %} + {% if FEATURES['ART_SUBMISSIONS'] -%} +
  • Approve or Reject Sidebar Images
  • +
  • Approve or Reject Banners
  • + {% endif %} {% if FEATURES['EMOJI_SUBMISSIONS'] -%}
  • Approve or Reject Emojis
  • {% endif %} diff --git a/files/templates/directory.html b/files/templates/directory.html index 84b1eb52f..a59ac8573 100644 --- a/files/templates/directory.html +++ b/files/templates/directory.html @@ -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 %}
    diff --git a/files/templates/submit_art.html b/files/templates/submit_art.html new file mode 100644 index 000000000..156464843 --- /dev/null +++ b/files/templates/submit_art.html @@ -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 %} +

    Submit {{kind}} {% if kind == 'Sidebar' %}Image{% endif %}

    +
    +
    +
    +
    + + + +
    +
    + + + +
    + + + + + +
    +
    +
    +
    + +

    Pending Approval

    +
    +
    +
    + {% for entry in entries %} +
    +
    +
    + + +
    + + +
    + + + + +
    +
    + {% if v.admin_level >= PERMS['MODERATE_PENDING_SUBMITTED_ASSETS'] or v.id == entry.submitter_id %} +
    + + + + {% if v.admin_level >= PERMS['MODERATE_PENDING_SUBMITTED_ASSETS'] %} + + {% endif %} +
    + {% endif %} +
    + {% endfor %} +
    +
    +
    + + +{% endblock %} diff --git a/files/templates/submit_emojis.html b/files/templates/submit_emojis.html index 94a8b12fe..d477ee1a1 100644 --- a/files/templates/submit_emojis.html +++ b/files/templates/submit_emojis.html @@ -1,68 +1,66 @@ -{% extends "default.html" %} +{% extends "submit_navbar.html" %} {% block pagetitle %}Submit Emojis{% endblock %} {% block pagetype %}message{% endblock %} {% block content %} -
    -

    Submit Emoji

    -
    -
    -
    -
    - +

    Submit Emoji

    +
    +
    +
    + + -
    -
    +
    +
    - - + + +
    + + +
    + +
    + + + + + + + + + + + {% if FEATURES['NSFW_MARKING'] %} +
    + +
    + {% endif %} - -
    - + +
    +
    -

    Pending Approval

    -
    +

    Pending Approval

    +
    {% for emoji in emojis %} diff --git a/files/templates/submit_hats.html b/files/templates/submit_hats.html index 8c374dbf2..60a4f5611 100644 --- a/files/templates/submit_hats.html +++ b/files/templates/submit_hats.html @@ -1,51 +1,49 @@ -{% extends "default.html" %} +{% extends "submit_navbar.html" %} {% block pagetitle %}Submit Hats{% endblock %} {% block pagetype %}message{% endblock %} {% block content %} -
    -

    Submit Hat

    -
    -
    -
    -
    - +

    Submit Hat

    +
    +
    +
    + + -
    -
    +
    +
    - - + + +
    + +
    + Hat Template — 100x130px (do not resize), circle is profile picture, do not include circle in final submission. +
    + + + + + + + + + + + +
    +
    -

    Pending Approval

    -
    +

    Pending Approval

    +
    {% for hat in hats %} diff --git a/files/templates/submit_navbar.html b/files/templates/submit_navbar.html new file mode 100644 index 000000000..f81bd155d --- /dev/null +++ b/files/templates/submit_navbar.html @@ -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 %} +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + {% endblock %} + +
    + {% block content %}{% endblock %} +
    +{% endblock %} diff --git a/migrations/20240304-add-art-submission-flow.sql b/migrations/20240304-add-art-submission-flow.sql new file mode 100644 index 000000000..64c36b6d1 --- /dev/null +++ b/migrations/20240304-add-art-submission-flow.sql @@ -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');