diff --git a/files/assets/js/bootstrap.js b/files/assets/js/bootstrap.js index 8b4ca23e5..95913ffef 100644 --- a/files/assets/js/bootstrap.js +++ b/files/assets/js/bootstrap.js @@ -218,6 +218,7 @@ function post_toast(t, url, button1, button2, classname, extra_actions) { } else { document.getElementById('toast-post-error-text').innerText = "Error, please try again later." if (data && data["error"]) document.getElementById('toast-post-error-text').innerText = data["error"]; + if (data && data["details"]) document.getElementById('toast-post-error-text').innerText = data["details"]; bootstrap.Toast.getOrCreateInstance(document.getElementById('toast-post-error')).show(); } if (!isShopConfirm) diff --git a/files/helpers/const.py b/files/helpers/const.py index 59b72bd7d..79975c89b 100644 --- a/files/helpers/const.py +++ b/files/helpers/const.py @@ -257,6 +257,73 @@ FEATURES = { 'PATRON_ICONS': False, } +WERKZEUG_ERROR_DESCRIPTIONS = { + 400: "The browser (or proxy) sent a request that this server could not understand.", + 401: "The server could not verify that you are authorized to access the URL requested. You either supplied the wrong credentials (e.g. a bad password), or your browser doesn't understand how to supply the credentials required.", + 403: "You don't have the permission to access the requested resource. It is either read-protected or not readable by the server.", + 404: "The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.", + 405: "The method is not allowed for the requested URL.", + 406: "The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request.", + 409: "A conflict happened while processing the request. The resource might have been modified while the request was being processed.", + 413: "The data value transmitted exceeds the capacity limit.", + 414: "The length of the requested URL exceeds the capacity limit for this server. The request cannot be processed.", + 415: "The server does not support the media type transmitted in the request.", + 417: "The server could not meet the requirements of the Expect header", + 418: "This server is a teapot, not a coffee machine", + 429: "This user has exceeded an allotted request count. Try again later.", + 500: "The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.", +} + +ERROR_TITLES = { + 400: "Bad Request", + 401: "Unauthorized", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Too Many Pings", + 409: "Conflict", + 413: "Max image/audio size is 8 MB (16 MB for paypigs)", + 414: "Max video size is 32 MB (64 MB for paypigs)", + 415: "Unsupported Media Type", + 417: "Image already exists!", + 418: "WEBM videos are not allowed", + 429: "Too Many Requests", + 500: "Internal Server Error", +} + +ERROR_MSGS = { + 400: "That request was bad and you should feel bad.", + 401: "What you're trying to do requires an account. I think. The original error message said something about a castle and I hated that.", + 403: "YOU AREN'T WELCOME HERE GO AWAY", + 404: "Someone typed something wrong and it was probably you, please do better.", + 405: "idk how anyone gets this error but if you see this, remember to follow @carpathianflorist
the original error text here talked about internet gremlins and wtf", + 406: "Max limit is 5 for comments and 50 for posts", + 409: "There's a conflict between what you're trying to do and what you or someone else has done and because of that you can't do what you're trying to do. So maybe like... don't try and do that? Sorry not sorry", + 413: "Max image/audio size is 8 MB (16 MB for paypigs)", + 414: "Max video size is 32 MB (64 MB for paypigs)", + 415: "Please upload only Image, Video, or Audio files!", + 417: "Image already exists!", + 418: "Please convert your video to MP4 and re-upload it!", + 429: "go spam somewhere else nerd", + 500: "Hiiiii it's carp! I think this error means that there's a timeout error. And I think that means something took too long to load so it decided not to work at all. If you keep seeing this on the same page but not other pages, then something is probably wrong with that specific function. It may not be called a function, but that sounds right to me. Anyway, ping me and I'll whine to someone smarter to fix it. Don't bother them. Thanks ily <3", +} + +ERROR_MARSEYS = { + 400: "marseybrainlet", + 401: "marseydead", + 403: "marseytroll", + 404: "marseyconfused", + 405: "marseyretard", + 406: "marseyrage", + 409: "marseynoyou", + 413: "marseyretard", + 414: "marseychonker2", + 415: "marseydetective", + 418: "marseytea", + 429: "marseyrentfree", + 500: "marseycarp3", +} + EMOJI_MARSEYS = True EMOJI_SRCS = ['files/assets/emojis.json'] @@ -389,6 +456,8 @@ elif SITE == 'pcmemes.net': PIN_LIMIT = 10 FEATURES['REPOST_DETECTION'] = False FEATURES['GAMBLING'] = False + ERROR_MSGS[500] = "Hiiiii it's nigger! I think this error means that there's a nigger error. And I think that means something took too long to load so it decided to be a nigger. If you keep seeing this on the same page but not other pages, then something its probably a niggerfaggot. It may not be called a nigger, but that sounds right to me. Anyway, ping me and I'll whine to someone smarter to fix it. Don't bother them. Thanks ily <3" + ERROR_MARSEYS[500] = "wholesome" POST_RATE_LIMIT = '1/second;4/minute;20/hour;100/day' HOLE_COST = 2000 diff --git a/files/routes/admin.py b/files/routes/admin.py index 401d0bd89..f430f34c7 100644 --- a/files/routes/admin.py +++ b/files/routes/admin.py @@ -196,7 +196,7 @@ def remove_admin(v, username): @admin_level_required(PERMS['POST_BETS_DISTRIBUTE']) def distribute(v, option_id): autojanny = get_account(AUTOJANNY_ID) - if autojanny.coins == 0: return {"error": "@AutoJanny has 0 coins"}, 400 + if autojanny.coins == 0: abort(400, "@AutoJanny has 0 coins") try: option_id = int(option_id) except: abort(400) @@ -303,7 +303,7 @@ def revert_actions(v, username): def club_allow(v, username): u = get_user(username, v=v) - if u.admin_level >= v.admin_level: return {"error": "noob"}, 400 + if u.admin_level >= v.admin_level: abort(403, 'noob') u.club_allowed = True g.db.add(u) @@ -329,7 +329,7 @@ def club_allow(v, username): def club_ban(v, username): u = get_user(username, v=v) - if u.admin_level >= v.admin_level: return {"error": "noob"}, 400 + if u.admin_level >= v.admin_level: abort(403, 'noob') u.club_allowed = False @@ -490,7 +490,7 @@ def purge_cache(v): g.db.add(ma) if response == "": return {"message": "Cache purged!"} - return {"error": "Failed to purge cache."}, 400 + abort(400, 'Failed to purge cache.') @app.post("/admin/under_attack") @@ -507,7 +507,7 @@ def under_attack(v): response = str(requests.patch(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/settings/security_level', headers=CF_HEADERS, data='{"value":"high"}', timeout=5)) if response == "": return {"message": "Under attack mode disabled!"} - return {"error": "Failed to disable under attack mode."}, 400 + abort(400, "Failed to disable under attack mode.") else: ma = ModAction( kind="enable_under_attack", @@ -517,7 +517,7 @@ def under_attack(v): response = str(requests.patch(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/settings/security_level', headers=CF_HEADERS, data='{"value":"under_attack"}', timeout=5)) if response == "": return {"message": "Under attack mode enabled!"} - return {"error": "Failed to enable under attack mode."}, 400 + abort(400, "Failed to enable under attack mode.") @app.get("/admin/badge_grant") @admin_level_required(PERMS['USER_BADGES']) @@ -1158,7 +1158,7 @@ def approve_post(post_id, v): post = get_post(post_id) if post.author.id == v.id and post.author.agendaposter and AGENDAPOSTER_PHRASE not in post.body.lower() and post.sub != 'chudrama': - return {"error": "You can't bypass the chud award!"}, 400 + abort(400, "You can't bypass the chud award!") if post.is_banned: ma=ModAction( @@ -1223,7 +1223,7 @@ def sticky_post(post_id, v): if v.admin_level >= PERMS['BYPASS_PIN_LIMIT']: post.stickied = v.username post.stickied_utc = int(time.time()) + 3600 - else: return {"error": f"Can't exceed {PIN_LIMIT} pinned posts limit!"}, 403 + else: abort(403, f"Can't exceed {PIN_LIMIT} pinned posts limit!") else: post.stickied = v.username g.db.add(post) @@ -1246,7 +1246,7 @@ def unsticky_post(post_id, v): post = get_post(post_id) if post.stickied: - if post.stickied.endswith('(pin award)'): return {"error": "Can't unpin award pins!"}, 403 + if post.stickied.endswith('(pin award)'): abort(403, "Can't unpin award pins!") post.stickied = None post.stickied_utc = None @@ -1296,7 +1296,7 @@ def unsticky_comment(cid, v): comment = get_comment(cid, v=v) if comment.stickied: - if comment.stickied.endswith("(pin award)"): return {"error": "Can't unpin award pins!"}, 403 + if comment.stickied.endswith("(pin award)"): abort(403, "Can't unpin award pins!") comment.stickied = None g.db.add(comment) @@ -1344,7 +1344,7 @@ def approve_comment(c_id, v): if not comment: abort(404) if comment.author.id == v.id and comment.author.agendaposter and AGENDAPOSTER_PHRASE not in comment.body.lower() and comment.post.sub != 'chudrama': - return {"error": "You can't bypass the chud award!"}, 400 + abort(400, "You can't bypass the chud award!") if comment.is_banned: ma=ModAction( diff --git a/files/routes/asset_submissions.py b/files/routes/asset_submissions.py index 26d0ae625..f10b8e457 100644 --- a/files/routes/asset_submissions.py +++ b/files/routes/asset_submissions.py @@ -96,27 +96,27 @@ if SITE not in ('pcmemes.net', 'watchpeopledie.co'): @admin_level_required(PERMS['MODERATE_PENDING_SUBMITTED_MARSEYS']) def approve_marsey(v, name): if AEVANN_ID and v.id not in (AEVANN_ID, CARP_ID, SNAKES_ID): - return {"error": "Only Carp can approve marseys!"}, 403 + abort(403, "Only Carp can approve marseys!") name = name.lower().strip() marsey = g.db.query(Marsey).filter_by(name=name).one_or_none() if not marsey: - return {"error": f"This marsey '{name}' doesn't exist!"}, 404 + abort(404, f"This marsey '{name}' doesn't exist!") tags = request.values.get('tags').lower().strip() if not tags: - return {"error": "You need to include tags!"}, 400 + abort(400, "You need to include tags!") new_name = request.values.get('name').lower().strip() if not new_name: - return {"error": "You need to include name!"}, 400 + abort(400, "You need to include name!") if not marsey_regex.fullmatch(new_name): - return {"error": "Invalid name!"}, 400 + abort(400, "Invalid name!") if not tags_regex.fullmatch(tags): - return {"error": "Invalid tags!"}, 400 + abort(400, "Invalid tags!") marsey.name = new_name @@ -168,10 +168,10 @@ if SITE not in ('pcmemes.net', 'watchpeopledie.co'): marsey = g.db.query(Marsey).filter_by(name=name).one_or_none() if not marsey: - return {"error": f"This marsey '{name}' doesn't exist!"}, 404 + abort(404, f"This marsey '{name}' doesn't exist!") if v.id not in (marsey.submitter_id, AEVANN_ID, CARP_ID): - return {"error": "Only Carp can remove marseys!"}, 403 + abort(403, "Only Carp can remove marseys!") if v.id != marsey.submitter_id: msg = f"@{v.username} has rejected a marsey you submitted: `'{marsey.name}'`" @@ -257,27 +257,20 @@ if SITE not in ('pcmemes.net', 'watchpeopledie.co'): @admin_level_required(PERMS['MODERATE_PENDING_SUBMITTED_HATS']) def approve_hat(v, name): if AEVANN_ID and v.id not in (AEVANN_ID, CARP_ID, SNAKES_ID): - return {"error": "Only Carp can approve hats!"}, 403 + abort(403, "Only Carp can approve hats!") name = name.strip() hat = g.db.query(HatDef).filter_by(name=name).one_or_none() - if not hat: - return {"error": f"This hat '{name}' doesn't exist!"}, 404 + if not hat: abort(404, f"This hat '{name}' doesn't exist!") description = request.values.get('description').strip() - if not description: - return {"error": "You need to include description!"}, 400 + if not description: abort(400, "You need to include a description!") new_name = request.values.get('name').strip() - if not new_name: - return {"error": "You need to include name!"}, 400 - - if not hat_regex.fullmatch(new_name): - return {"error": "Invalid name!"}, 400 - - if not description_regex.fullmatch(description): - return {"error": "Invalid description!"}, 400 + if not new_name: abort(400, "You need to include a name!") + if not hat_regex.fullmatch(new_name): abort(400, "Invalid name!") + if not description_regex.fullmatch(description): abort(400, "Invalid description!") hat.price = int(request.values.get('price')) hat.name = new_name @@ -332,11 +325,9 @@ if SITE not in ('pcmemes.net', 'watchpeopledie.co'): name = name.strip() hat = g.db.query(HatDef).filter_by(name=name).one_or_none() - if not hat: - return {"error": f"This hat '{name}' doesn't exist!"}, 404 - + if not hat: abort(404, f"This hat '{name}' doesn't exist!") if v.id not in (hat.submitter_id, AEVANN_ID, CARP_ID): - return {"error": "Only Carp can remove hats!"}, 403 + abort(403, 'Only Carp can remove hats!') if v.id != hat.submitter_id: msg = f"@{v.username} has rejected a hat you submitted: `'{hat.name}'`" diff --git a/files/routes/awards.py b/files/routes/awards.py index faecb388d..a7bdce2c7 100644 --- a/files/routes/awards.py +++ b/files/routes/awards.py @@ -49,10 +49,10 @@ def buy(v, award): if award == 'benefactor' and not request.values.get("mb"): - return {"error": "You can only buy the Benefactor award with marseybux."}, 403 + abort(403, "You can only buy the Benefactor award with marseybux.") if award == 'ghost' and v.admin_level < PERMS['BUY_GHOST_AWARD']: - return {"error": "Only admins can buy this award."}, 403 + abort(403, "Only admins can buy this award") AWARDS = deepcopy(AWARDS2) @@ -67,15 +67,15 @@ def buy(v, award): if request.values.get("mb"): if award == "grass": - return {"error": "You can't buy the grass award with marseybux."}, 403 + abort(403, "You can't buy the grass award with marseybux.") charged = v.charge_account('procoins', price) if not charged: - return {"error": "Not enough marseybux."}, 400 + abort(400, "Not enough marseybux.") else: charged = v.charge_account('coins', price) if not charged: - return {"error": "Not enough coins."}, 400 + abort(400, "Not enough coins.") v.coins_spent += price if v.coins_spent >= 1000000: @@ -129,8 +129,6 @@ def buy(v, award): @is_not_permabanned @feature_required('BADGES') def award_thing(v, thing_type, id): - - if thing_type == 'post': thing = get_post(id) else: thing = get_comment(id) @@ -142,8 +140,7 @@ def award_thing(v, thing_type, id): if v.house: AWARDS[v.house] = HOUSE_AWARDS[v.house] - if kind not in AWARDS: - return {"error": "This award doesn't exist."}, 404 + if kind not in AWARDS: abort(404, "This award doesn't exist") award = g.db.query(AwardRelationship).filter( AwardRelationship.kind == kind, @@ -152,8 +149,7 @@ def award_thing(v, thing_type, id): AwardRelationship.comment_id == None ).first() - if not award: - return {"error": "You don't have that award."}, 404 + if not award: abort(404, "You don't have that award") if thing_type == 'post': award.submission_id = thing.id else: award.comment_id = thing.id @@ -167,13 +163,13 @@ def award_thing(v, thing_type, id): if author.shadowbanned: abort(404) if SITE == 'rdrama.net' and author.id in (PIZZASHILL_ID, CARP_ID): - return {"error": "This user is immune to awards."}, 403 + abort(403, "This user is immune to awards.") if kind == "benefactor" and author.id == v.id: - return {"error": "You can't use this award on yourself."}, 400 + abort(400, "You can't use this award on yourself.") if kind == 'marsify' and author.marsify == 1: - return {"error": "User is already permanently marsified!"}, 403 + abort(403, "User is already permanently marsified!") if v.id != author.id: safe_username = "👻" if thing.ghost else f"@{author.username}" @@ -254,7 +250,7 @@ def award_thing(v, thing_type, id): g.db.add(thing) elif kind == "agendaposter" and not (author.agendaposter and author.agendaposter == 0): if author.marseyawarded: - return {"error": "This user is the under the effect of a conflicting award: Marsey award."}, 404 + abort(400, "This user is under the effect of a conflicting award: Marsey award.") if author.agendaposter and time.time() < author.agendaposter: author.agendaposter += 86400 else: author.agendaposter = int(time.time()) + 86400 @@ -284,13 +280,13 @@ def award_thing(v, thing_type, id): badge_grant(user=author, badge_id=98) elif kind == "pizzashill": if author.bird: - return {"error": "This user is the under the effect of a conflicting award: Bird Site award."}, 404 + abort(400, "This user is under the effect of a conflicting award: Bird Site award.") if author.longpost: author.longpost += 86400 else: author.longpost = int(time.time()) + 86400 badge_grant(user=author, badge_id=97) elif kind == "bird": if author.longpost: - return {"error": "This user is the under the effect of a conflicting award: Pizzashill award."}, 404 + abort(400, "This user is under the effect of a conflicting award: Pizzashill award.") if author.bird: author.bird += 86400 else: author.bird = int(time.time()) + 86400 badge_grant(user=author, badge_id=95) @@ -317,7 +313,7 @@ def award_thing(v, thing_type, id): else: author.progressivestack = int(time.time()) + 21600 badge_grant(user=author, badge_id=94) elif kind == "benefactor": - if author.patron: return {"error": "This user is already a paypig!"}, 400 + if author.patron: abort(400, "This user is already a paypig!") author.patron = 1 if author.patron_utc: author.patron_utc += 2629746 else: author.patron_utc = int(time.time()) + 2629746 diff --git a/files/routes/casino.py b/files/routes/casino.py index 5208f6615..c88194361 100644 --- a/files/routes/casino.py +++ b/files/routes/casino.py @@ -55,8 +55,8 @@ def casino_game_page(v, game): @auth_required @feature_required('GAMBLING') def casino_game_feed(v, game): - if v.rehab: - return {"error": "You are under Rehab award effect!"}, 400 + if v.rehab: + abort(403, "You are under Rehab award effect!") elif game not in CASINO_GAME_KINDS: abort(404) @@ -83,27 +83,27 @@ def lottershe(v): @feature_required('GAMBLING') def pull_slots(v): if v.rehab: - return {"error": "You are under Rehab award effect!"}, 400 + abort(403, "You are under Rehab award effect!") try: wager = int(request.values.get("wager")) except: - return {"error": "Invalid wager."}, 400 + abort(400, "Invalid wager.") try: currency = request.values.get("currency") except: - return {"error": "Invalid currency (expected 'coin' or 'marseybux')."}, 400 + abort(400, "Invalid currency (expected 'coin' or 'marseybux').") if (currency == "coin" and wager > v.coins) or (currency == "marseybux" and wager > v.procoins): - return {"error": f"Not enough {currency} to make that bet."}, 400 + abort(400, f"Not enough {currency} to make that bet") success, game_state = casino_slot_pull(v, wager, currency) if success: return {"game_state": game_state, "gambler": {"coins": v.coins, "procoins": v.procoins}} else: - return {"error": f"Wager must be more than 5 {currency}."}, 400 + abort(400, f"Wager must be more than 5 {currency}") # 21 @@ -113,7 +113,7 @@ def pull_slots(v): @feature_required('GAMBLING') def blackjack_deal_to_player(v): if v.rehab: - return {"error": "You are under Rehab award effect!"}, 400 + abort(403, "You are under Rehab award effect!") try: wager = int(request.values.get("wager")) @@ -124,7 +124,7 @@ def blackjack_deal_to_player(v): return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}} except Exception as e: - return {"error": str(e)}, 400 + abort(400, str(e)) @app.post("/casino/twentyone/hit") @@ -133,14 +133,14 @@ def blackjack_deal_to_player(v): @feature_required('GAMBLING') def blackjack_player_hit(v): if v.rehab: - return {"error": "You are under Rehab award effect!"}, 400 + abort(403, "You are under Rehab award effect!") try: state = dispatch_action(v, BlackjackAction.HIT) feed = get_game_feed('blackjack') return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}} except: - return {"error": "Unable to hit."}, 400 + abort(400, "Unable to hit.") @app.post("/casino/twentyone/stay") @@ -149,14 +149,14 @@ def blackjack_player_hit(v): @feature_required('GAMBLING') def blackjack_player_stay(v): if v.rehab: - return {"error": "You are under Rehab award effect!"}, 400 + abort(403, "You are under Rehab award effect!") try: state = dispatch_action(v, BlackjackAction.STAY) feed = get_game_feed('blackjack') return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}} except: - return {"error": "Unable to stay."}, 400 + abort(400, "Unable to stay.") @app.post("/casino/twentyone/double-down") @@ -165,14 +165,14 @@ def blackjack_player_stay(v): @feature_required('GAMBLING') def blackjack_player_doubled_down(v): if v.rehab: - return {"error": "You are under Rehab award effect!"}, 400 + abort(403, "You are under Rehab award effect!") try: state = dispatch_action(v, BlackjackAction.DOUBLE_DOWN) feed = get_game_feed('blackjack') return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}} except: - return {"error": "Unable to double down."}, 400 + abort(400, "Unable to double down.") @app.post("/casino/twentyone/buy-insurance") @@ -181,14 +181,14 @@ def blackjack_player_doubled_down(v): @feature_required('GAMBLING') def blackjack_player_bought_insurance(v): if v.rehab: - return {"error": "You are under Rehab award effect!"}, 400 + abort(403, "You are under Rehab award effect!") try: state = dispatch_action(v, BlackjackAction.BUY_INSURANCE) feed = get_game_feed('blackjack') return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}} except: - return {"error": "Unable to buy insurance."}, 400 + abort(403, "Unable to buy insurance.") # Roulette @app.get("/casino/roulette/bets") @@ -197,7 +197,7 @@ def blackjack_player_bought_insurance(v): @feature_required('GAMBLING') def roulette_get_bets(v): if v.rehab: - return {"error": "You are under Rehab award effect!"}, 400 + abort(403, "You are under Rehab award effect!") bets = get_roulette_bets() @@ -210,7 +210,7 @@ def roulette_get_bets(v): @feature_required('GAMBLING') def roulette_player_placed_bet(v): if v.rehab: - return {"error": "You are under Rehab award effect!"}, 400 + abort(403, "You are under Rehab award effect!") try: bet = request.values.get("bet") @@ -227,4 +227,4 @@ def roulette_player_placed_bet(v): return {"success": True, "bets": bets, "gambler": {"coins": v.coins, "procoins": v.procoins}} except: - return {"error": "Unable to place a bet."}, 400 + abort(400, "Unable to place a bet.") diff --git a/files/routes/comments.py b/files/routes/comments.py index 6b3f74b7a..2075668ab 100644 --- a/files/routes/comments.py +++ b/files/routes/comments.py @@ -56,7 +56,7 @@ def post_pid_comment_cid(cid, pid=None, anything=None, v=None, sub=None): post = get_post(pid, v=v) if post.over_18 and not (v and v.over_18) and not session.get('over_18', 0) >= int(time.time()): - if request.headers.get("Authorization"): return {"error": 'This content is not suitable for some users and situations.'}, 403 + if request.headers.get("Authorization"): abort(403, "This content is not suitable for some users and situations.") else: return render_template("errors/nsfw.html", v=v) try: context = min(int(request.values.get("context", 0)), 8) @@ -126,17 +126,17 @@ def post_pid_comment_cid(cid, pid=None, anything=None, v=None, sub=None): @limiter.limit("1/second;20/minute;200/hour;1000/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}') @auth_required def comment(v): - if v.is_suspended: return {"error": "You can't perform this action while banned."}, 403 + if v.is_suspended: abort(403, "You can't perform this action while banned.") parent_submission = request.values.get("submission").strip() parent_fullname = request.values.get("parent_fullname").strip() parent_post = get_post(parent_submission, v=v) sub = parent_post.sub - if sub and v.exiled_from(sub): return {"error": f"You're exiled from /h/{sub}"}, 403 + if sub and v.exiled_from(sub): abort(403, f"You're exiled from /h/{sub}") if sub in ('furry','vampire','racist','femboy') and not v.client and not v.house.lower().startswith(sub): - return {"error": f"You need to be a member of House {sub.capitalize()} to comment in /h/{sub}"}, 400 + abort(403, f"You need to be a member of House {sub.capitalize()} to comment in /h/{sub}") if parent_post.club and not (v and (v.paid_dues or v.id == parent_post.author_id)): abort(403) @@ -157,18 +157,17 @@ def comment(v): if not parent.can_see(v): abort(404) if parent.deleted_utc != 0: abort(404) - if level > COMMENT_MAX_DEPTH: - return {"error": f"Max comment level is {COMMENT_MAX_DEPTH}"}, 400 + if level > COMMENT_MAX_DEPTH: abort(400, f"Max comment level is {COMMENT_MAX_DEPTH}") body = sanitize_raw_body(request.values.get("body", ""), False) if parent_post.id not in ADMIGGERS: if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')): - return {"error":"You have to type more than 280 characters!"}, 403 + abort(403, "You have to type more than 280 characters!") elif v.bird and len(body) > 140: - return {"error":"You have to type less than 140 characters!"}, 403 + abort(403, "You have to type less than 140 characters!") - if not body and not request.files.get('file'): return {"error":"You need to actually write something!"}, 400 + if not body and not request.files.get('file'): abort(400, "You need to actually write something!") options = [] for i in poll_regex.finditer(body): @@ -187,7 +186,7 @@ def comment(v): oldname = f'/images/{time.time()}'.replace('.','') + '.webp' file.save(oldname) image = process_image(oldname, patron=v.patron) - if image == "": return {"error":"Image upload failed"}, 400 + if image == "": abort(400, "Image upload failed") if v.admin_level >= PERMS['SITE_SETTINGS_SIDEBARS_BANNERS_BADGES'] and level == 1: if parent_post.id == SIDEBAR_THREAD: li = sorted(os.listdir(f'files/assets/images/{SITE_NAME}/sidebar'), @@ -210,7 +209,7 @@ def comment(v): name = badge_def["name"] existing = g.db.query(BadgeDef).filter_by(name=name).one_or_none() - if existing: return {"error": "A badge with this name already exists!"}, 403 + if existing: abort(403, "A badge with this name already exists!") badge = BadgeDef(name=name, description=badge_def["description"]) g.db.add(badge) @@ -221,7 +220,7 @@ def comment(v): requests.post(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/purge_cache', headers=CF_HEADERS, data=f'{{"files": ["https://{SITE}/assets/images/badges/{badge.id}.webp"]}}', timeout=5) except Exception as e: - return {"error": str(e)}, 400 + abort(400, str(e)) body += f"\n\n![]({image})" elif file.content_type.startswith('video/'): body += f"\n\n{process_video(file)}" @@ -257,7 +256,7 @@ def comment(v): if existing: return {"error": f"You already made that comment: /comment/{existing.id}"}, 409 if parent.author.any_block_exists(v) and v.admin_level < PERMS['POST_COMMENT_MODERATION']: - return {"error": "You can't reply to users who have blocked you or users that you have blocked."}, 403 + abort(403, "You can't reply to users who have blocked you or users that you have blocked.") is_bot = v.id != BBBB_ID and (bool(request.headers.get("Authorization")) or (SITE == 'pcmemes.net' and v.id == SNAPPY_ID)) @@ -297,7 +296,7 @@ def comment(v): g.db.add(ma) g.db.commit() - return {"error": "Too much spam!"}, 403 + abort(403, "Too much spam!") if len(body_html) > COMMENT_BODY_HTML_LENGTH_LIMIT: abort(400) @@ -442,7 +441,7 @@ def comment(v): g.db.add(c) if v.marseyawarded and parent_post.id not in ADMIGGERS and marseyaward_body_regex.search(body_html): - return {"error":"You can only type marseys!"}, 403 + abort(403, "You can only type marseys!") check_for_treasure(body, c) @@ -469,7 +468,7 @@ def edit_comment(cid, v): c = get_comment(cid, v=v) if time.time() - c.created_utc > 7*24*60*60 and not (c.post and c.post.private): - return {"error":"You can't edit comments older than 1 week!"}, 403 + abort(403, "You can't edit comments older than 1 week!") if c.author_id != v.id: abort(403) if not c.post: abort(403) @@ -477,13 +476,13 @@ def edit_comment(cid, v): body = sanitize_raw_body(request.values.get("body", ""), False) if len(body) < 1 and not (request.files.get("file") and request.headers.get("cf-ipcountry") != "T1"): - return {"error":"You have to actually type something!"}, 400 + abort(400, "You have to actually type something!") if body != c.body or request.files.get("file") and request.headers.get("cf-ipcountry") != "T1": if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')): - return {"error":"You have to type more than 280 characters!"}, 403 + abort(403, "You have to type more than 280 characters!") elif v.bird and len(body) > 140: - return {"error":"You have to type less than 140 characters!"}, 403 + abort(403, "You have to type less than 140 characters!") for i in poll_regex.finditer(body): body = body.replace(i.group(0), "") @@ -535,7 +534,7 @@ def edit_comment(cid, v): g.db.add(comment) g.db.commit() - return {"error": "Too much spam!"}, 403 + abort(403, "Too much spam!") body += process_files() body = body.strip()[:COMMENT_BODY_LENGTH_LIMIT] # process_files potentially adds characters to the post @@ -553,7 +552,7 @@ def edit_comment(cid, v): if len(body_html) > COMMENT_BODY_HTML_LENGTH_LIMIT: abort(400) if v.marseyawarded and marseyaward_body_regex.search(body_html): - return {"error":"You can only type marseys!"}, 403 + abort(403, "You can only type marseys!") c.body = body c.body_html = body_html @@ -568,7 +567,7 @@ def edit_comment(cid, v): g.db.add(notif) if v.agendaposter and not v.marseyawarded and AGENDAPOSTER_PHRASE not in c.body.lower() and c.post.sub != 'chudrama': - return {"error": f'You have to include "{AGENDAPOSTER_PHRASE}" in your comment!'}, 403 + abort(403, f'You have to include "{AGENDAPOSTER_PHRASE}" in your comment!') if int(time.time()) - c.created_utc > 60 * 3: c.edited_utc = int(time.time()) @@ -745,7 +744,6 @@ def diff_words(answer, guess): @limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}') @auth_required def handle_wordle_action(cid, v): - comment = get_comment(cid) if v.id != comment.author_id: @@ -757,8 +755,7 @@ def handle_wordle_action(cid, v): try: guess = request.values.get("thing").strip().lower() except: abort(400) - if len(guess) != 5: - return {"error": "Not a valid guess!"}, 400 + if len(guess) != 5: abort(400, "Not a valid guess!") if status == "active": guesses += "".join(cg + WORDLE_COLOR_MAPPINGS[diff] for cg, diff in zip(guess, diff_words(answer, guess))) diff --git a/files/routes/discord.py b/files/routes/discord.py index 2d2f31cc7..fff30625e 100644 --- a/files/routes/discord.py +++ b/files/routes/discord.py @@ -8,15 +8,10 @@ import requests @app.get("/discord") @is_not_permabanned def join_discord(v): - - if v.shadowbanned: return {"error": "Internal server error"}, 400 - + if v.shadowbanned: return {"error": "Internal Server Error"}, 500 now=int(time.time()) - state=generate_hash(f"{now}+{v.id}+discord") - state=f"{now}.{state}" - return redirect(f"https://discord.com/api/oauth2/authorize?client_id={DISCORD_CLIENT_ID}&redirect_uri=https%3A%2F%2F{SITE}%2Fdiscord_redirect&response_type=code&scope=identify%20guilds.join&state={state}") diff --git a/files/routes/errors.py b/files/routes/errors.py index 484b89871..4712fad93 100644 --- a/files/routes/errors.py +++ b/files/routes/errors.py @@ -4,17 +4,39 @@ from urllib.parse import quote, urlencode import time from files.__main__ import app, limiter - +# If you're adding an error, go here: +# https://github.com/pallets/werkzeug/blob/main/src/werkzeug/exceptions.py +# and copy the description for the error code you're adding and add it to +# the constant WERKZEUG_ERROR_DESCRIPTIONS so that the default error message +# doesn't show up on the message. Be exact or it won't work properly. @app.errorhandler(400) -def error_400(e): - if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "400 Bad Request"}, 400 - else: return render_template('errors/400.html', err=True), 400 +@app.errorhandler(403) +@app.errorhandler(404) +@app.errorhandler(405) +@app.errorhandler(406) +@app.errorhandler(409) +@app.errorhandler(413) +@app.errorhandler(414) +@app.errorhandler(415) +@app.errorhandler(417) +@app.errorhandler(418) +@app.errorhandler(429) +def error(e): + title = ERROR_TITLES.get(e.code, str(e.code)) + msg = ERROR_MSGS.get(e.code, str(e.code)) + details = e.description + + if WERKZEUG_ERROR_DESCRIPTIONS.get(e.code, None) == details: + details = None + if request.headers.get("Authorization") or request.headers.get("xhr"): + return {"error": title, "code": e.code, "description": msg, "details": details} + img = ERROR_MARSEYS.get(e.code, 'marseyl') + return render_template('errors/error.html', err=True, title=title, msg=msg, details=details, img=img), e.code @app.errorhandler(401) def error_401(e): - - if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "401 Not Authorized"}, 401 + if request.headers.get("Authorization") or request.headers.get("xhr"): return error(e) else: path = request.path qs = urlencode(dict(request.values)) @@ -23,75 +45,10 @@ def error_401(e): if session.get("history"): return redirect(f"/login?redirect={argval}") else: return redirect(f"/signup?redirect={argval}") -@app.errorhandler(406) -def error_406(e): - if request.headers.get("Authorization") or request.headers.get("xhr"): - return {"error": "Too many pings: max limit is 5 for comments and 50 for posts"}, 406 - else: return render_template('errors/406.html', err=True), 406 - -@app.errorhandler(403) -def error_403(e): - - description = e.description - if description == "You don't have the permission to access the requested resource. It is either read-protected or not readable by the server.": description = '' - - if request.headers.get("Authorization") or request.headers.get("xhr"): - if not description: description = "403 Forbidden" - return {"error": description}, 403 - else: - if not description: description = "YOU AREN'T WELCOME HERE GO AWAY" - return render_template('errors/403.html', description=description, err=True), 403 - - -@app.errorhandler(404) -def error_404(e): - if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "404 Not Found"}, 404 - else: return render_template('errors/404.html', err=True), 404 - -@app.errorhandler(405) -def error_405(e): - if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "405 Method Not Allowed"}, 405 - else: return render_template('errors/405.html', err=True), 405 - -@app.errorhandler(413) -def error_413(e): - if request.headers.get("Authorization") or request.headers.get("xhr"): - return {"error": "Max image/audio size is 8 MB (16 MB for paypigs)"}, 413 - else: return render_template('errors/413.html', err=True), 413 - -@app.errorhandler(414) -def error_414(e): - if request.headers.get("Authorization") or request.headers.get("xhr"): - return {"error": "Max video size is 32 MB (64 MB for paypigs)"}, 414 - else: return render_template('errors/414.html', err=True), 414 - -@app.errorhandler(415) -def error_415(e): - if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "Please upload only Image, Video, or Audio files!"}, 415 - else: return render_template('errors/415.html', err=True), 415 - -@app.errorhandler(417) -def error_417(e): - return {"error": "Image already exists!"}, 417 - -@app.errorhandler(418) -def error_418(e): - if request.headers.get("Authorization") or request.headers.get("xhr"): - return {"error": "WEBM videos are not allowed, please convert your video to MP4 and re-upload it!"}, 418 - else: return render_template('errors/418.html', err=True), 418 - -@app.errorhandler(429) -def error_429(e): - if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "429 Too Many Requests"}, 429 - else: return render_template('errors/429.html', err=True), 429 - - @app.errorhandler(500) def error_500(e): g.db.rollback() - - if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "500 Internal Server Error"}, 500 - else: return render_template('errors/500.html', err=True), 500 + return error(e) @app.post("/allow_nsfw") diff --git a/files/routes/hats.py b/files/routes/hats.py index 4eff02150..a78d445e2 100644 --- a/files/routes/hats.py +++ b/files/routes/hats.py @@ -36,25 +36,23 @@ def hats(v): @feature_required('HATS') def buy_hat(v, hat_id): try: hat_id = int(hat_id) - except: return {"error": "Hat not found!"}, 400 + except: abort(404, "Hat not found!") hat = g.db.query(HatDef).filter_by(submitter_id=None, id=hat_id).one_or_none() - if not hat: return {"error": "Hat not found!"}, 400 + if not hat: abort(404, "Hat not found!") existing = g.db.query(Hat).filter_by(user_id=v.id, hat_id=hat.id).one_or_none() - if existing: return {"error": "You already own this hat!"}, 400 + if existing: abort(400, "You already own this hat!") if request.values.get("mb"): charged = v.charge_account('procoins', hat.price) - if not charged: - return {"error": "Not enough marseybux."}, 400 + if not charged: abort(400, "Not enough marseybux.") hat.author.procoins += hat.price * 0.1 currency = "marseybux" else: charged = v.charge_account('coins', hat.price) - if not charged: - return {"error": "Not enough coins."}, 400 + if not charged: abort(400, "Not enough coins.") v.coins_spent_on_hats += hat.price hat.author.coins += hat.price * 0.1 @@ -86,10 +84,10 @@ def buy_hat(v, hat_id): @feature_required('HATS') def equip_hat(v, hat_id): try: hat_id = int(hat_id) - except: return {"error": "Hat not found!"}, 400 + except: abort(404, "Hat not found!") hat = g.db.query(Hat).filter_by(hat_id=hat_id, user_id=v.id).one_or_none() - if not hat: return {"error": "You don't own this hat!"}, 400 + if not hat: abort(403, "You don't own this hat!") hat.equipped = True g.db.add(hat) @@ -101,10 +99,10 @@ def equip_hat(v, hat_id): @feature_required('HATS') def unequip_hat(v, hat_id): try: hat_id = int(hat_id) - except: return {"error": "Hat not found!"}, 400 + except: abort(404, "Hat not found!") hat = g.db.query(Hat).filter_by(hat_id=hat_id, user_id=v.id).one_or_none() - if not hat: return {"error": "You don't own this hat!"}, 400 + if not hat: abort(403, "You don't own this hat!") hat.equipped = False g.db.add(hat) diff --git a/files/routes/lottery.py b/files/routes/lottery.py index 01544317b..930b56205 100644 --- a/files/routes/lottery.py +++ b/files/routes/lottery.py @@ -29,7 +29,7 @@ def lottery_start(v): @feature_required('GAMBLING') def lottery_buy(v): try: quantity = int(request.values.get("quantity")) - except: return {"error": "Invalid ticket quantity."}, 400 + except: abort(400, "Invalid ticket quantity.") success, message = purchase_lottery_tickets(v, quantity) lottery, participants = get_active_lottery_stats() diff --git a/files/routes/polls.py b/files/routes/polls.py index b75cfc68b..71e6ab5f2 100644 --- a/files/routes/polls.py +++ b/files/routes/polls.py @@ -19,10 +19,10 @@ def vote_option(option_id, v): sub = option.post.sub if sub in ('furry','vampire','racist','femboy') and not v.house.lower().startswith(sub): - return {"error": f"You need to be a member of House {sub.capitalize()} to vote on polls in /h/{sub}"}, 400 + abort(403, f"You need to be a member of House {sub.capitalize()} to vote on polls in /h/{sub}") if option.exclusive == 2: - if v.coins < POLL_BET_COINS: return {"error": f"You don't have {POLL_BET_COINS} coins!"}, 400 + if v.coins < POLL_BET_COINS: abort(400, f"You don't have {POLL_BET_COINS} coins!") v.coins -= POLL_BET_COINS g.db.add(v) autojanny = get_account(AUTOJANNY_ID) @@ -35,7 +35,7 @@ def vote_option(option_id, v): SubmissionOptionVote.submission_id==option.submission_id, SubmissionOption.exclusive==option.exclusive).one_or_none() if vote: - if option.exclusive == 2: return {"error": "You already voted on this bet!"}, 400 + if option.exclusive == 2: abort(400, "You already voted on this bet!") g.db.delete(vote) existing = g.db.query(SubmissionOptionVote).filter_by(option_id=option_id, user_id=v.id).one_or_none() @@ -85,7 +85,7 @@ def vote_option_comment(option_id, v): sub = option.comment.post.sub if sub in ('furry','vampire','racist','femboy') and not v.house.lower().startswith(sub): - return {"error": f"You need to be a member of House {sub.capitalize()} to vote on polls in /h/{sub}"}, 400 + abort(403, f"You need to be a member of House {sub.capitalize()} to vote on polls in /h/{sub}") if option.exclusive: vote = g.db.query(CommentOptionVote).join(CommentOption).filter( diff --git a/files/routes/posts.py b/files/routes/posts.py index ee49fc665..3851a9a3c 100644 --- a/files/routes/posts.py +++ b/files/routes/posts.py @@ -414,26 +414,26 @@ def edit_post(pid, v): # Disable edits on things older than 1wk unless it's a draft or editor is a jannie if (time.time() - p.created_utc > 7*24*60*60 and not p.private and not v.admin_level >= PERMS['POST_EDITING']): - return {"error": "You can't edit posts older than 1 week!"}, 403 + abort(403, "You can't edit posts older than 1 week!") title = sanitize_raw_title(request.values.get("title", "")) body = sanitize_raw_body(request.values.get("body", ""), True) if v.id == p.author_id: if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')): - return {"error":"You have to type more than 280 characters!"}, 403 + abort(403, "You have to type more than 280 characters!") elif v.bird and len(body) > 140: - return {"error":"You have to type less than 140 characters!"}, 403 + abort(403, "You have to type less than 140 characters!") if not title: - return {"error": "Please enter a better title."}, 400 + abort(400, "Please enter a better title.") if title != p.title: torture = (v.agendaposter and not v.marseyawarded and p.sub != 'chudrama' and v.id == p.author_id) title_html = filter_emojis_only(title, golden=False, torture=torture) if v.id == p.author_id and v.marseyawarded and not marseyaward_title_regex.fullmatch(title_html): - return {"error":"You can only type marseys!"}, 403 + abort(403, "You can only type marseys!") p.title = title p.title_html = title_html @@ -466,7 +466,7 @@ def edit_post(pid, v): body_html = sanitize(body, golden=False, limit_pings=100, showmore=False, torture=torture) if v.id == p.author_id and v.marseyawarded and marseyaward_body_regex.search(body_html): - return {"error":"You can only type marseys!"}, 403 + abort(403, "You can only type marseys!") p.body = body @@ -477,12 +477,13 @@ def edit_post(pid, v): g.db.add(v) send_repeatable_notification(CARP_ID, p.permalink) - if len(body_html) > POST_BODY_HTML_LENGTH_LIMIT: return {"error":f"Submission body_html too long! (max {POST_BODY_HTML_LENGTH_LIMIT} characters)"}, 400 + if len(body_html) > POST_BODY_HTML_LENGTH_LIMIT: + abort(400, f"Submission body_html too long! (max {POST_BODY_HTML_LENGTH_LIMIT} characters)") p.body_html = body_html if v.id == p.author_id and v.agendaposter and not v.marseyawarded and AGENDAPOSTER_PHRASE not in f'{p.body}{p.title}'.lower() and p.sub != 'chudrama': - return {"error": f'You have to include "{AGENDAPOSTER_PHRASE}" in your post!'}, 403 + abort(403, f'You have to include "{AGENDAPOSTER_PHRASE}" in your post!') if not p.private and not p.ghost: @@ -702,7 +703,7 @@ def submit_post(v, sub=None): body = sanitize_raw_body(request.values.get("body", ""), True) def error(error): - if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": error}, 400 + if request.headers.get("Authorization") or request.headers.get("xhr"): abort(400, error) SUBS = [x[0] for x in g.db.query(Sub.name).order_by(Sub.name).all()] return render_template("submit.html", SUBS=SUBS, v=v, error=error, title=title, url=url, body=body), 400 @@ -1204,7 +1205,7 @@ def pin_post(post_id, v): post = get_post(post_id) if post: - if v.id != post.author_id: return {"error": "Only the post author's can do that!"}, 400 + if v.id != post.author_id: abort(400, "Only the post author's can do that!") post.is_pinned = not post.is_pinned g.db.add(post) @@ -1212,7 +1213,7 @@ def pin_post(post_id, v): if post.is_pinned: return {"message": "Post pinned!"} else: return {"message": "Post unpinned!"} - return {"error": "Post not found!"}, 400 + return abort(404, "Post not found!") extensions = ( diff --git a/files/routes/reporting.py b/files/routes/reporting.py index 45c000498..dc7d2baa3 100644 --- a/files/routes/reporting.py +++ b/files/routes/reporting.py @@ -21,14 +21,13 @@ def flag_post(pid, v): if not v.is_banned: v.ban_reason = 'Blackjack' send_repeatable_notification(CARP_ID, f"reports on {post.permalink}") - if v.is_muted: - return {"error": "You are forbidden from making reports."}, 400 + if v.is_muted: abort(400, "You are forbidden from making reports.") reason = reason[:100] reason = filter_emojis_only(reason) - if len(reason) > 350: return {"error": "Too long."}, 400 + if len(reason) > 350: abort(400, "Too long.") if reason.startswith('!') and (v.admin_level >= PERMS['POST_COMMENT_MODERATION'] or post.sub and v.mods(post.sub)): post.flair = reason[1:] @@ -58,16 +57,16 @@ def flag_post(pid, v): sub_to = g.db.get(Sub, sub_to) sub_to = sub_to.name if sub_to else None - if sub_from == sub_to: {"error": f"Post is already in /h/{sub_to}"}, 400 + if sub_from == sub_to: abort(400, f"Post is already in /h/{sub_to}") if post.author.exiled_from(sub_to): - return {"error": f"User is exiled from this {HOLE_NAME}!"}, 400 + abort(400, f"User is exiled from this {HOLE_NAME}!") if sub_to in ('furry','vampire','racist','femboy') and not v.client and not post.author.house.lower().startswith(sub_to): if v.id == post.author_id: - return {"error": f"You need to be a member of House {sub.capitalize()} to post in /h/{sub}"}, 403 + abort(403, f"You need to be a member of House {sub_to.capitalize()} to post in /h/{sub_to}") else: - return {"error": f"@{post.author.username} needs to be a member of House {sub.capitalize()} for their post to be moved to /h/{sub}"}, 400 + abort(403, f"@{post.author.username} needs to be a member of House {sub_to.capitalize()} for their post to be moved to /h/{sub_to}") post.sub = sub_to g.db.add(post) @@ -103,8 +102,7 @@ def flag_post(pid, v): return {"message": f"Post moved to /h/{post.sub}"} else: existing = g.db.query(Flag.post_id).filter_by(user_id=v.id, post_id=post.id).one_or_none() - if existing: - return {"error": "You already reported this post!"}, 409 + if existing: abort(409, "You already reported this post!") flag = Flag(post_id=post.id, user_id=v.id, reason=reason) g.db.add(flag) @@ -121,8 +119,7 @@ def flag_comment(cid, v): comment = get_comment(cid) existing = g.db.query(CommentFlag.comment_id).filter_by(user_id=v.id, comment_id=comment.id).one_or_none() - if existing: - return {"error": "You already reported this comment!"}, 409 + if existing: abort(409, "You already reported this comment!") reason = request.values.get("reason", "").strip() @@ -135,7 +132,7 @@ def flag_comment(cid, v): reason = filter_emojis_only(reason) - if len(reason) > 350: return {"error": "Too long."}, 400 + if len(reason) > 350: abort(400, "Too long.") flag = CommentFlag(comment_id=comment.id, user_id=v.id, reason=reason) diff --git a/files/routes/search.py b/files/routes/search.py index 24257bbaf..addfb9c37 100644 --- a/files/routes/search.py +++ b/files/routes/search.py @@ -75,7 +75,7 @@ def searchposts(v): author = get_user(criteria['author'], v=v, include_shadowbanned=False) if author.is_private and author.id != v.id and v.admin_level < PERMS['VIEW_PRIVATE_PROFILES'] and not v.eye: if request.headers.get("Authorization"): - return {"error": f"@{author.username}'s profile is private; You can't use the 'author' syntax on them"}, 400 + abort(403, f"@{author.username}'s profile is private; You can't use the 'author' syntax on them") return render_template("search.html", v=v, query=query, @@ -193,7 +193,7 @@ def searchcomments(v): if 'post' in criteria: try: post = int(criteria['post']) - except: return {"error": f"Post with id {post} does not exist."}, 400 + except: abort(404, f"Post with id {post} does not exist.") comments = comments.filter(Comment.parent_submission == post) @@ -202,7 +202,7 @@ def searchcomments(v): author = get_user(criteria['author'], v=v, include_shadowbanned=False) if author.is_private and author.id != v.id and v.admin_level < PERMS['VIEW_PRIVATE_PROFILES'] and not v.eye: if request.headers.get("Authorization"): - return {"error": f"@{author.username}'s profile is private; You can't use the 'author' syntax on them"}, 400 + abort(403, f"@{author.username}'s profile is private; You can't use the 'author' syntax on them") return render_template("search_comments.html", v=v, query=query, total=0, page=page, comments=[], sort=sort, t=t, next_exists=False, error=f"@{author.username}'s profile is private; You can't use the 'author' syntax on them.") diff --git a/files/routes/settings.py b/files/routes/settings.py index 5c731e485..470546840 100644 --- a/files/routes/settings.py +++ b/files/routes/settings.py @@ -263,7 +263,7 @@ def settings_profile_post(v): if theme: if theme in {"4chan","classic","classic_dark","coffee","dark","dramblr","light","midnight","transparent","tron","win98"}: if theme == "transparent" and not v.background: - return {"error": "You need to set a background to use the transparent theme!"}, 400 + abort(400, "You need to set a background to use the transparent theme!") v.theme = theme if theme == "win98": v.themecolor = "30409f" updated = True @@ -294,7 +294,7 @@ def settings_profile_post(v): return {"message": "Your settings have been updated."} else: - return {"error": "You didn't change anything."}, 400 + abort(400, "You didn't change anything.") @app.post("/settings/filters") @@ -348,23 +348,23 @@ def themecolor(v): @auth_required def gumroad(v): if not (v.email and v.is_activated): - return {"error": f"You must have a verified email to verify {patron} status and claim your rewards!"}, 400 + abort(400, f"You must have a verified email to verify {patron} status and claim your rewards!") data = {'access_token': GUMROAD_TOKEN, 'email': v.email} response = requests.get('https://api.gumroad.com/v2/sales', data=data, timeout=5).json()["sales"] - if len(response) == 0: return {"error": "Email not found"}, 404 + if len(response) == 0: abort(404, "Email not found") response = [x for x in response if x['variants_and_quantity']] response = response[0] tier = tiers[response["variants_and_quantity"]] - if v.patron == tier: return {"error": f"{patron} rewards already claimed"}, 400 + if v.patron == tier: abort(400, f"{patron} rewards already claimed") procoins = procoins_li[tier] - procoins_li[v.patron] - if procoins < 0: return {"error": f"{patron} rewards already claimed"}, 400 + if procoins < 0: abort(400, f"{patron} rewards already claimed") existing = g.db.query(User.id).filter(User.email == v.email, User.is_activated == True, User.patron >= tier).first() - if existing: return {"error": f"{patron} rewards already claimed on another account"}, 400 + if existing: abort(400, f"{patron} rewards already claimed on another account") v.patron = tier if v.discord_id: add_role(v, f"{tier}") @@ -513,7 +513,7 @@ def settings_log_out_others(v): @limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}') @auth_required def settings_images_profile(v): - if request.headers.get("cf-ipcountry") == "T1": return {"error":"Image uploads are not allowed through TOR."}, 403 + if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.") file = request.files["profile"] @@ -549,9 +549,7 @@ def settings_images_profile(v): @auth_required @feature_required('USERS_PROFILE_BANNER') def settings_images_banner(v): - - - if request.headers.get("cf-ipcountry") == "T1": return {"error":"Image uploads are not allowed through TOR."}, 403 + if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.") file = request.files["banner"] @@ -586,12 +584,12 @@ def settings_css_get(v): @limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}') @auth_required def settings_css(v): - if v.agendaposter: return {"error": "Agendapostered users can't edit css!"}, 400 + if v.agendaposter: abort(400, "Agendapostered users can't edit CSS!") css = request.values.get("css").strip().replace('\\', '').strip()[:4000] if '= PERMS['USER_BLOCKS_VISIBLE']: diff --git a/files/routes/subs.py b/files/routes/subs.py index 3cf32fb3f..8784b0f9e 100644 --- a/files/routes/subs.py +++ b/files/routes/subs.py @@ -245,7 +245,7 @@ def add_mod(v, sub): user = get_user(user, v=v, include_shadowbanned=False) if sub in ('furry','vampire','racist','femboy') and not v.client and not user.house.lower().startswith(sub): - return {"error": f"@{user.username} needs to be a member of House {sub.capitalize()} to be added as a mod there!"}, 400 + abort(403, f"@{user.username} needs to be a member of House {sub.capitalize()} to be added as a mod there!") existing = g.db.query(Mod).filter_by(user_id=user.id, sub=sub).one_or_none() @@ -470,7 +470,7 @@ def get_sub_css(sub): @limiter.limit("1/second;10/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}') @is_not_permabanned def sub_banner(v, sub): - if request.headers.get("cf-ipcountry") == "T1": return {"error":"Image uploads are not allowed through TOR."}, 403 + if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.") sub = get_sub_by_name(sub) if not v.mods(sub.name): abort(403) @@ -503,7 +503,7 @@ def sub_banner(v, sub): @limiter.limit("1/second;10/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}') @is_not_permabanned def sub_sidebar(v, sub): - if request.headers.get("cf-ipcountry") == "T1": return {"error":"Image uploads are not allowed through TOR."}, 403 + if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.") sub = get_sub_by_name(sub) if not v.mods(sub.name): abort(403) @@ -535,7 +535,7 @@ def sub_sidebar(v, sub): @limiter.limit("1/second;10/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}') @is_not_permabanned def sub_marsey(v, sub): - if request.headers.get("cf-ipcountry") == "T1": return {"error":"Image uploads are not allowed through TOR."}, 403 + if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.") sub = get_sub_by_name(sub) if not v.mods(sub.name): abort(403) diff --git a/files/routes/users.py b/files/routes/users.py index 635c0016b..f6e5e7da2 100644 --- a/files/routes/users.py +++ b/files/routes/users.py @@ -264,9 +264,9 @@ def transfer_coins(v, username): amount = int(amount) if amount.isdigit() else None reason = request.values.get("reason", "").strip() - if amount is None or amount <= 0: return {"error": "Invalid amount of coins."}, 400 - if v.coins < amount: return {"error": "You don't have enough coins."}, 400 - if amount < 100: return {"error": "You have to gift at least 100 coins."}, 400 + if amount is None or amount <= 0: abort(400, "Invalid amount of coins.") + if v.coins < amount: abort(400, "You don't have enough coins.") + if amount < 100: abort(400, "You have to gift at least 100 coins.") if not v.patron and not receiver.patron and not v.alts_patron and not receiver.alts_patron: tax = math.ceil(amount*0.03) else: tax = 0 @@ -291,8 +291,7 @@ def transfer_coins(v, username): g.db.add(v) return {"message": f"{amount-tax} coins have been transferred to @{receiver.username}"}, 200 - - return {"message": "You can't transfer coins to yourself!"}, 400 + abort(400, "You can't transfer coins to yourself!") @app.post("/@/transfer_bux") @@ -307,9 +306,9 @@ def transfer_bux(v, username): amount = int(amount) if amount.isdigit() else None reason = request.values.get("reason", "").strip() - if not amount or amount < 0: return {"error": "Invalid amount of marseybux."}, 400 - if v.procoins < amount: return {"error": "You don't have enough marseybux"}, 400 - if amount < 100: return {"error": "You have to gift at least 100 marseybux."}, 400 + if not amount or amount < 0: abort(400, "Invalid amount of marseybux.") + if v.procoins < amount: abort(400, "You don't have enough marseybux") + if amount < 100: abort(400, "You have to gift at least 100 marseybux.") v.procoins -= amount @@ -331,7 +330,7 @@ def transfer_bux(v, username): g.db.add(v) return {"message": f"{amount} marseybux have been transferred to @{receiver.username}"}, 200 - return {"message": "You can't transfer marseybux to yourself!"}, 400 + abort(400, "You can't transfer marseybux to yourslef!") @app.get("/leaderboard") @@ -500,16 +499,16 @@ def message2(v, username): user = get_user(username, v=v, include_blocks=True, include_shadowbanned=False) if hasattr(user, 'is_blocking') and user.is_blocking: - return {"error": "You're blocking this user."}, 403 + abort(403, "You're blocking this user.") if v.admin_level <= PERMS['MESSAGE_BLOCKED_USERS'] and hasattr(user, 'is_blocked') and user.is_blocked: - return {"error": "This user is blocking you."}, 403 + abort(403, "This user is blocking you.") message = request.values.get("message", "").strip()[:10000].strip() - if not message: return {"error": "Message is empty!"}, 400 + if not message: abort(400, "Message is empty!") - if 'linkedin.com' in message: return {"error": "This domain 'linkedin.com' is banned."}, 403 + if 'linkedin.com' in message: abort(403, "This domain 'linkedin.com' is banned.") body_html = sanitize(message) @@ -519,7 +518,7 @@ def message2(v, username): Comment.body_html == body_html, ).first() - if existing: return {"error": "Message already exists."}, 403 + if existing: abort(403, "Message already exists.") c = Comment(author_id=v.id, parent_submission=None, @@ -573,18 +572,18 @@ def messagereply(v): body = request.values.get("body", "").strip().replace('‎','') body = body.replace('\r\n', '\n')[:COMMENT_BODY_LENGTH_LIMIT] - if not body and not request.files.get("file"): return {"error": "Message is empty!"}, 400 + if not body and not request.files.get("file"): abort(400, "Message is empty!") - if 'linkedin.com' in body: return {"error": "this domain 'linkedin.com' is banned"}, 400 + if 'linkedin.com' in body: abort(400, "this domain 'linkedin.com' is banned") id = int(request.values.get("parent_id")) parent = get_comment(id, v=v) user_id = parent.author.id if v.is_suspended_permanently and parent.sentto != 2: - return {"error": "You are permabanned and may not reply to messages."}, 400 + abort(400, "You are permabanned and may not reply to messages.") elif v.is_muted and parent.sentto == 2: - return {"error": "You are forbidden from replying to modmail."}, 400 + abort(400, "You are forbidden from replying to modmail.") if parent.sentto == 2: user_id = None elif v.id == user_id: user_id = parent.sentto @@ -791,14 +790,14 @@ def u_username(username, v=None): if u.is_private and (not v or (v.id != u.id and v.admin_level < PERMS['VIEW_PRIVATE_PROFILES'] and not v.eye)): if request.headers.get("Authorization") or request.headers.get("xhr") or request.path.endswith(".json"): - return {"error": "This userpage is private"}, 403 + abort(403, "This userpage is private") return render_template("userpage_private.html", u=u, v=v) if v and hasattr(u, 'is_blocking') and u.is_blocking: if request.headers.get("Authorization") or request.headers.get("xhr") or request.path.endswith(".json"): - return {"error": f"You are blocking @{u.username}."}, 403 + abort(403, f"You are blocking @{u.username}.") return render_template("userpage_blocking.html", u=u, v=v) @@ -877,12 +876,12 @@ def u_username_comments(username, v=None): if u.is_private and (not v or (v.id != u.id and v.admin_level < PERMS['VIEW_PRIVATE_PROFILES'] and not v.eye)): if request.headers.get("Authorization") or request.headers.get("xhr") or request.path.endswith(".json"): - return {"error": "This userpage is private"}, 403 + abort(403, "This userpage is private") return render_template("userpage_private.html", u=u, v=v) if v and hasattr(u, 'is_blocking') and u.is_blocking: if request.headers.get("Authorization") or request.headers.get("xhr") or request.path.endswith(".json"): - return {"error": f"You are blocking @{u.username}."}, 403 + abort(403, f"You are blocking @{u.username}.") return render_template("userpage_blocking.html", u=u, v=v) try: page = max(int(request.values.get("page", "1")), 1) @@ -933,9 +932,9 @@ def u_username_info(username, v=None): user=get_user(username, v=v, include_blocks=True, include_shadowbanned=False) if hasattr(user, 'is_blocking') and user.is_blocking: - return {"error": "You're blocking this user."}, 401 + abort(401, "You're blocking this user.") elif hasattr(user, 'is_blocked') and user.is_blocked: - return {"error": "This user is blocking you."}, 403 + abort(403, "This user is blocking you.") return user.json @@ -946,9 +945,9 @@ def u_user_id_info(id, v=None): user=get_account(id, v=v, include_blocks=True, include_shadowbanned=False) if hasattr(user, 'is_blocking') and user.is_blocking: - return {"error": "You're blocking this user."}, 401 + abort(403, "You're blocking this user.") elif hasattr(user, 'is_blocked') and user.is_blocked: - return {"error": "This user is blocking you."}, 403 + abort(403, "This user is blocking you.") return user.json @@ -961,10 +960,10 @@ def follow_user(username, v): target = get_user(username, v=v, include_shadowbanned=False) if target.id==v.id: - return {"error": "You can't follow yourself!"}, 400 + abort(400, "You can't follow yourself!") if target.is_nofollow: - return {"error": "This user has disallowed other users from following them!"}, 403 + abort(403, "This user has disallowed other users from following them!") if g.db.query(Follow).filter_by(user_id=v.id, target_id=target.id).one_or_none(): return {"message": f"@{target.username} has been followed!"} @@ -993,7 +992,7 @@ def unfollow_user(username, v): if target.fish: if not v.shadowbanned: send_notification(target.id, f"@{v.username} has tried to unfollow you and failed because of your fish award!") - return {"error": "You can't unfollow this user!"}, 400 + abort(400, "You can't unfollow this user!") follow = g.db.query(Follow).filter_by(user_id=v.id, target_id=target.id).one_or_none() @@ -1223,15 +1222,15 @@ kofi_tiers={ @auth_required def settings_kofi(v): if not (v.email and v.is_activated): - return {"error": f"You must have a verified email to verify {patron} status and claim your rewards!"}, 400 + abort(400, f"You must have a verified email to verify {patron} status and claim your rewards!") transaction = g.db.query(Transaction).filter_by(email=v.email).order_by(Transaction.created_utc.desc()).first() if not transaction: - return {"error": "Email not found"}, 404 + abort(404, "Email not found") if transaction.claimed: - return {"error": f"{patron} rewards already claimed"}, 400 + abort(400, f"{patron} rewards already claimed") tier = kofi_tiers[transaction.amount] diff --git a/files/routes/votes.py b/files/routes/votes.py index f98204a9d..3d4a897b8 100644 --- a/files/routes/votes.py +++ b/files/routes/votes.py @@ -49,7 +49,7 @@ def vote_info_get(v, link): @is_not_permabanned def vote_post(post_id, new, v): - if new == "-1" and DISABLE_DOWNVOTES: return {"error": "forbidden."}, 403 + if new == "-1" and DISABLE_DOWNVOTES: abort(403) if new not in ["-1", "0", "1"]: abort(400) @@ -126,7 +126,7 @@ def vote_post(post_id, new, v): @is_not_permabanned def vote_comment(comment_id, new, v): - if new == "-1" and DISABLE_DOWNVOTES: return {"error": "forbidden."}, 403 + if new == "-1" and DISABLE_DOWNVOTES: abort(403, "forbidden.") if new not in ["-1", "0", "1"]: abort(400) diff --git a/files/templates/errors/400.html b/files/templates/errors/400.html deleted file mode 100644 index 894d59c7a..000000000 --- a/files/templates/errors/400.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "default.html" %} - -{% block title %} -400 Bad Request -{% endblock %} - -{% block pagetype %}error-400{% endblock %} - -{% block content %} -
-
-
- :#marseybrainlet: -

-			

400 Bad Request

-

That request was bad and you should feel bad.

-
-
-
-{% endblock %} diff --git a/files/templates/errors/401.html b/files/templates/errors/401.html deleted file mode 100644 index 6403b0553..000000000 --- a/files/templates/errors/401.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "default.html" %} - -{% block title %} -401 Not Authorized -{% endblock %} - -{% block pagetype %}error-401{% endblock %} - -{% block content %} -
-
-
- - :#marseydead: -

-
-			

401 Not Authorized

-

What you're trying to do requires an account. I think. The original error message said something about a castle and I hated that.

- - -
-
-
-{% endblock %} \ No newline at end of file diff --git a/files/templates/errors/403.html b/files/templates/errors/403.html deleted file mode 100644 index ae6535659..000000000 --- a/files/templates/errors/403.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "default.html" %} - -{% block title %} -403 Forbidden -{% endblock %} - -{% block pagetype %}error-403{% endblock %} - -{% block content %} -
-
-
- :#marseytroll: -

-		

403 Forbidden

-

{{description}}

- -
-
-
-{% endblock %} diff --git a/files/templates/errors/404.html b/files/templates/errors/404.html deleted file mode 100644 index a5b7ce074..000000000 --- a/files/templates/errors/404.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "default.html" %} - -{% block title %} -404 Page Not Found -{% endblock %} - -{% block pagetype %}error-404{% endblock %} - -{% block content %} -
-
-
- :#marseyconfused -

-		

404 Page Not Found

-

Someone typed something wrong and it was probably you, please do better.

- -
-
-
-{% endblock %} diff --git a/files/templates/errors/405.html b/files/templates/errors/405.html deleted file mode 100644 index 33215384a..000000000 --- a/files/templates/errors/405.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "default.html" %} - -{% block title %} -405 Method Not Allowed -{% endblock %} - -{% block pagetype %}error-405{% endblock %} - -{% block content %} -
-
-
- :#marseyretard: -

-		

405 Method Not Allowed

-

idk how anyone gets this error but if you see this, remember to follow @carpathianflorist
the original error text here talked about internet gremlins and wtf

- -
-
-
-{% endblock %} diff --git a/files/templates/errors/406.html b/files/templates/errors/406.html deleted file mode 100644 index 2b36f518c..000000000 --- a/files/templates/errors/406.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "default.html" %} - -{% block title %} -Too many pings -{% endblock %} - -{% block pagetype %}Too many pings{% endblock %} - -{% block content %} -
-
-
- :#marseyrage -

-		

Too many pings

-

Max limit is 5 for comments and 50 for posts

- -
-
-
-{% endblock %} diff --git a/files/templates/errors/413.html b/files/templates/errors/413.html deleted file mode 100644 index 3dae7f9b8..000000000 --- a/files/templates/errors/413.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "default.html" %} - -{% block title %} -Max image/audio size is 8 MB (16 MB for paypigs) -{% endblock %} - -{% block pagetype %}error-413{% endblock %} - -{% block content %} -
-
-
- :#marseyretard: -

-		

Max image/audio size is 8 MB (16 MB for paypigs)

- -
-
-
-{% endblock %} diff --git a/files/templates/errors/414.html b/files/templates/errors/414.html deleted file mode 100644 index e054bf531..000000000 --- a/files/templates/errors/414.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "default.html" %} - -{% block title %} -Max video size is 32 MB (64 MB for paypigs) -{% endblock %} - -{% block pagetype %}error-414{% endblock %} - -{% block content %} -
-
-
- :#marseyretard: -

-		

Max video size is 32 MB (64 MB for paypigs)

- -
-
-
-{% endblock %} diff --git a/files/templates/errors/415.html b/files/templates/errors/415.html deleted file mode 100644 index 0139fd86d..000000000 --- a/files/templates/errors/415.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "default.html" %} - -{% block title %} -415 Unsupported Media Type -{% endblock %} - -{% block pagetype %}error-415{% endblock %} - -{% block content %} -
-
-
- :#marseydetective: -

-		

415 Unsupported Media Type

-

Please upload only Image, Video, or Audio files!

-
-
-
-{% endblock %} diff --git a/files/templates/errors/418.html b/files/templates/errors/418.html deleted file mode 100644 index 15642a384..000000000 --- a/files/templates/errors/418.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "default.html" %} - -{% block title %} -WEBM videos are not allowed -{% endblock %} - -{% block pagetype %}error-418{% endblock %} - -{% block content %} -
-
-
- :#marseytea: -

-		

WEBM videos are not allowed

- -
-
-
-{% endblock %} \ No newline at end of file diff --git a/files/templates/errors/429.html b/files/templates/errors/429.html deleted file mode 100644 index 0860ae1f2..000000000 --- a/files/templates/errors/429.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "default.html" %} - -{% block title %} -429 Too Many Requests -{% endblock %} - -{% block pagetype %}error-429{% endblock %} - -{% block content %} -
-
-
- :#marseyrentfree: -

-		

429 Too Many Requests

-

go spam somewhere else nerd

-
-
-
-{% endblock %} diff --git a/files/templates/errors/500.html b/files/templates/errors/500.html deleted file mode 100644 index ce0e69e84..000000000 --- a/files/templates/errors/500.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "default.html" %} - -{% block title %} -500 Internal Server Error -{% endblock %} - -{% block pagetype %}error-500{% endblock %} - -{% block content %} -
-
-
- {% if SITE_NAME == 'PCM' %} - wholesome - {% else %} - :#marseycarp3: - {% endif %} -

-			

500 Internal Server Error

-

- {% if SITE_NAME == 'PCM' %} - Hiiiii it's nigger! I think this error means that there's a nigger error. And I think that means something took too long to load so it decided to be a nigger. If you keep seeing this on the same page but not other pages, then something its probably a niggerfaggot. It may not be called a nigger, but that sounds right to me. Anyway, ping me and I'll whine to someone smarter to fix it. Don't bother them. Thanks ily <3 - {% else %} - Hiiiii it's carp! I think this error means that there's a timeout error. And I think that means something took too long to load so it decided not to work at all. If you keep seeing this on the same page but not other pages, then something is probably wrong with that specific function. It may not be called a function, but that sounds right to me. Anyway, ping me and I'll whine to someone smarter to fix it. Don't bother them. Thanks ily <3 - {% endif %} -

- -
-
-
-{% endblock %} diff --git a/files/templates/errors/error.html b/files/templates/errors/error.html new file mode 100644 index 000000000..c29d284b9 --- /dev/null +++ b/files/templates/errors/error.html @@ -0,0 +1,26 @@ +{% extends "default.html" %} + +{% block title %} +{{code}} {{title}} +{% endblock %} + +{% block pagetype %}error-{{code}}{% endblock %} + +{% block content %} +
+
+
+ {% if img -%} + :#{{img}}: + {%- endif %} +

+			

{{code}} {{title}}

+

{{msg|safe}}

+ {% if details -%} +
{{details|safe}}
+ {%- endif %} + +
+
+
+{% endblock %}