import qrcode import io import time import math from files.classes.views import ViewerRelationship from files.helpers.alerts import * from files.helpers.sanitize import * from files.helpers.strings import sql_ilike_clean from files.helpers.const import * from files.helpers.assetcache import assetcache_path from files.mail import * from flask import * from files.__main__ import app, limiter, db_session from pusher_push_notifications import PushNotifications from collections import Counter import gevent from sys import stdout if PUSHER_ID != 'blahblahblah': beams_client = PushNotifications(instance_id=PUSHER_ID, secret_key=PUSHER_KEY) def pusher_thread2(interests, notifbody, username): beams_client.publish_to_interests( interests=[interests], publish_body={ 'web': { 'notification': { 'title': f'New message from @{username}', 'body': notifbody, 'deep_link': f'{SITE_FULL}/notifications?messages=true', 'icon': SITE_FULL + assetcache_path(f'images/{SITE_ID}/icon.webp'), } }, 'fcm': { 'notification': { 'title': f'New message from @{username}', 'body': notifbody, }, 'data': { 'url': '/notifications?messages=true', } } }, ) stdout.flush() def leaderboard_thread(): global users9, users9_25, users13, users13_25 db = db_session() votes1 = db.query(Submission.author_id, func.count(Submission.author_id)).join(Vote, Vote.submission_id==Submission.id).filter(Vote.vote_type==-1).group_by(Submission.author_id).order_by(func.count(Submission.author_id).desc()).all() votes2 = db.query(Comment.author_id, func.count(Comment.author_id)).join(CommentVote, CommentVote.comment_id==Comment.id).filter(CommentVote.vote_type==-1).group_by(Comment.author_id).order_by(func.count(Comment.author_id).desc()).all() votes3 = Counter(dict(votes1)) + Counter(dict(votes2)) users8 = db.query(User).filter(User.id.in_(votes3.keys())).all() users9 = [] for user in users8: users9.append((user, votes3[user.id])) users9 = sorted(users9, key=lambda x: x[1], reverse=True) users9_25 = users9[:25] votes1 = db.query(Vote.user_id, func.count(Vote.user_id)).filter(Vote.vote_type==1).group_by(Vote.user_id).order_by(func.count(Vote.user_id).desc()).all() votes2 = db.query(CommentVote.user_id, func.count(CommentVote.user_id)).filter(CommentVote.vote_type==1).group_by(CommentVote.user_id).order_by(func.count(CommentVote.user_id).desc()).all() votes3 = Counter(dict(votes1)) + Counter(dict(votes2)) users14 = db.query(User).filter(User.id.in_(votes3.keys())).all() users13 = [] for user in users14: users13.append((user, votes3[user.id]-user.post_count-user.comment_count)) users13 = sorted(users13, key=lambda x: x[1], reverse=True) users13_25 = users13[:25] db.close() stdout.flush() if app.config["ENABLE_SERVICES"]: gevent.spawn(leaderboard_thread()) @app.get("/@/upvoters//posts") @admin_level_required(3) def upvoters_posts(v, username, uid): u = get_user(username) if u.is_private and v.id != u.id: abort(403) id = u.id uid = int(uid) page = max(1, int(request.values.get("page", 1))) listing = g.db.query(Submission).join(Vote, Vote.submission_id==Submission.id).filter(Submission.ghost == False, Submission.is_banned == False, Submission.deleted_utc == 0, Vote.vote_type==1, Submission.author_id==id, Vote.user_id==uid).order_by(Submission.created_utc.desc()).offset(25 * (page - 1)).limit(26).all() listing = [p.id for p in listing] next_exists = len(listing) > 25 listing = listing[:25] listing = get_posts(listing, v=v) return render_template("voted_posts.html", next_exists=next_exists, listing=listing, page=page, v=v) @app.get("/@/upvoters//comments") @admin_level_required(3) def upvoters_comments(v, username, uid): u = get_user(username) if u.is_private and v.id != u.id: abort(403) id = u.id uid = int(uid) page = max(1, int(request.values.get("page", 1))) listing = g.db.query(Comment).join(CommentVote, CommentVote.comment_id==Comment.id).filter(Comment.ghost == False, Comment.is_banned == False, Comment.deleted_utc == 0, CommentVote.vote_type==1, Comment.author_id==id, CommentVote.user_id==uid).order_by(Comment.created_utc.desc()).offset(25 * (page - 1)).limit(26).all() listing = [c.id for c in listing] next_exists = len(listing) > 25 listing = listing[:25] listing = get_comments(listing, v=v) return render_template("voted_comments.html", next_exists=next_exists, listing=listing, page=page, v=v, standalone=True) @app.get("/@/downvoters//posts") @admin_level_required(3) def downvoters_posts(v, username, uid): u = get_user(username) if u.is_private and v.id != u.id: abort(403) id = u.id uid = int(uid) page = max(1, int(request.values.get("page", 1))) listing = g.db.query(Submission).join(Vote, Vote.submission_id==Submission.id).filter(Submission.ghost == False, Submission.is_banned == False, Submission.deleted_utc == 0, Vote.vote_type==-1, Submission.author_id==id, Vote.user_id==uid).order_by(Submission.created_utc.desc()).offset(25 * (page - 1)).limit(26).all() listing = [p.id for p in listing] next_exists = len(listing) > 25 listing = listing[:25] listing = get_posts(listing, v=v) return render_template("voted_posts.html", next_exists=next_exists, listing=listing, page=page, v=v) @app.get("/@/downvoters//comments") @admin_level_required(3) def downvoters_comments(v, username, uid): u = get_user(username) if u.is_private and v.id != u.id: abort(403) id = u.id uid = int(uid) page = max(1, int(request.values.get("page", 1))) listing = g.db.query(Comment).join(CommentVote, CommentVote.comment_id==Comment.id).filter(Comment.ghost == False, Comment.is_banned == False, Comment.deleted_utc == 0, CommentVote.vote_type==-1, Comment.author_id==id, CommentVote.user_id==uid).order_by(Comment.created_utc.desc()).offset(25 * (page - 1)).limit(26).all() listing = [c.id for c in listing] next_exists = len(listing) > 25 listing = listing[:25] listing = get_comments(listing, v=v) return render_template("voted_comments.html", next_exists=next_exists, listing=listing, page=page, v=v, standalone=True) @app.get("/@/upvoting//posts") @admin_level_required(3) def upvoting_posts(v, username, uid): u = get_user(username) if u.is_private and v.id != u.id: abort(403) id = u.id uid = int(uid) page = max(1, int(request.values.get("page", 1))) listing = g.db.query(Submission).join(Vote, Vote.submission_id==Submission.id).filter(Submission.ghost == False, Submission.is_banned == False, Submission.deleted_utc == 0, Vote.vote_type==1, Vote.user_id==id, Submission.author_id==uid).order_by(Submission.created_utc.desc()).offset(25 * (page - 1)).limit(26).all() listing = [p.id for p in listing] next_exists = len(listing) > 25 listing = listing[:25] listing = get_posts(listing, v=v) return render_template("voted_posts.html", next_exists=next_exists, listing=listing, page=page, v=v) @app.get("/@/upvoting//comments") @admin_level_required(3) def upvoting_comments(v, username, uid): u = get_user(username) if u.is_private and v.id != u.id: abort(403) id = u.id uid = int(uid) page = max(1, int(request.values.get("page", 1))) listing = g.db.query(Comment).join(CommentVote, CommentVote.comment_id==Comment.id).filter(Comment.ghost == False, Comment.is_banned == False, Comment.deleted_utc == 0, CommentVote.vote_type==1, CommentVote.user_id==id, Comment.author_id==uid).order_by(Comment.created_utc.desc()).offset(25 * (page - 1)).limit(26).all() listing = [c.id for c in listing] next_exists = len(listing) > 25 listing = listing[:25] listing = get_comments(listing, v=v) return render_template("voted_comments.html", next_exists=next_exists, listing=listing, page=page, v=v, standalone=True) @app.get("/@/downvoting//posts") @admin_level_required(3) def downvoting_posts(v, username, uid): u = get_user(username) if u.is_private and v.id != u.id: abort(403) id = u.id uid = int(uid) page = max(1, int(request.values.get("page", 1))) listing = g.db.query(Submission).join(Vote, Vote.submission_id==Submission.id).filter(Submission.ghost == False, Submission.is_banned == False, Submission.deleted_utc == 0, Vote.vote_type==-1, Vote.user_id==id, Submission.author_id==uid).order_by(Submission.created_utc.desc()).offset(25 * (page - 1)).limit(26).all() listing = [p.id for p in listing] next_exists = len(listing) > 25 listing = listing[:25] listing = get_posts(listing, v=v) return render_template("voted_posts.html", next_exists=next_exists, listing=listing, page=page, v=v) @app.get("/@/downvoting//comments") @admin_level_required(3) def downvoting_comments(v, username, uid): u = get_user(username) if u.is_private and v.id != u.id: abort(403) id = u.id uid = int(uid) page = max(1, int(request.values.get("page", 1))) listing = g.db.query(Comment).join(CommentVote, CommentVote.comment_id==Comment.id).filter(Comment.ghost == False, Comment.is_banned == False, Comment.deleted_utc == 0, CommentVote.vote_type==-1, CommentVote.user_id==id, Comment.author_id==uid).order_by(Comment.created_utc.desc()).offset(25 * (page - 1)).limit(26).all() listing = [c.id for c in listing] next_exists = len(listing) > 25 listing = listing[:25] listing = get_comments(listing, v=v) return render_template("voted_comments.html", next_exists=next_exists, listing=listing, page=page, v=v, standalone=True) @app.get("/@/upvoters") @admin_level_required(3) def upvoters(v, username): id = get_user(username).id votes = g.db.query(Vote.user_id, func.count(Vote.user_id)).join(Submission, Vote.submission_id==Submission.id).filter(Submission.ghost == False, Submission.is_banned == False, Submission.deleted_utc == 0, Vote.vote_type==1, Submission.author_id==id).group_by(Vote.user_id).order_by(func.count(Vote.user_id).desc()).all() votes2 = g.db.query(CommentVote.user_id, func.count(CommentVote.user_id)).join(Comment, CommentVote.comment_id==Comment.id).filter(Comment.ghost == False, Comment.is_banned == False, Comment.deleted_utc == 0, CommentVote.vote_type==1, Comment.author_id==id).group_by(CommentVote.user_id).order_by(func.count(CommentVote.user_id).desc()).all() votes = Counter(dict(votes)) + Counter(dict(votes2)) users = g.db.query(User).filter(User.id.in_(votes.keys())).all() users2 = [] for user in users: users2.append((user, votes[user.id])) users = sorted(users2, key=lambda x: x[1], reverse=True) try: pos = [x[0].id for x in users].index(v.id) pos = (pos+1, users[pos][1]) except: pos = (len(users)+1, 0) return render_template("voters.html", v=v, users=users[:25], pos=pos, name='Up', name2=f'Who upvotes @{username}') @app.get("/@/downvoters") @admin_level_required(3) def downvoters(v, username): id = get_user(username).id votes = g.db.query(Vote.user_id, func.count(Vote.user_id)).join(Submission, Vote.submission_id==Submission.id).filter(Submission.ghost == False, Submission.is_banned == False, Submission.deleted_utc == 0, Vote.vote_type==-1, Submission.author_id==id).group_by(Vote.user_id).order_by(func.count(Vote.user_id).desc()).all() votes2 = g.db.query(CommentVote.user_id, func.count(CommentVote.user_id)).join(Comment, CommentVote.comment_id==Comment.id).filter(Comment.ghost == False, Comment.is_banned == False, Comment.deleted_utc == 0, CommentVote.vote_type==-1, Comment.author_id==id).group_by(CommentVote.user_id).order_by(func.count(CommentVote.user_id).desc()).all() votes = Counter(dict(votes)) + Counter(dict(votes2)) users = g.db.query(User).filter(User.id.in_(votes.keys())).all() users2 = [] for user in users: users2.append((user, votes[user.id])) users = sorted(users2, key=lambda x: x[1], reverse=True) try: pos = [x[0].id for x in users].index(v.id) pos = (pos+1, users[pos][1]) except: pos = (len(users)+1, 0) return render_template("voters.html", v=v, users=users[:25], pos=pos, name='Down', name2=f'Who downvotes @{username}') @app.get("/@/upvoting") @admin_level_required(3) def upvoting(v, username): id = get_user(username).id votes = g.db.query(Submission.author_id, func.count(Submission.author_id)).join(Vote, Vote.submission_id==Submission.id).filter(Submission.ghost == False, Submission.is_banned == False, Submission.deleted_utc == 0, Vote.vote_type==1, Vote.user_id==id).group_by(Submission.author_id).order_by(func.count(Submission.author_id).desc()).all() votes2 = g.db.query(Comment.author_id, func.count(Comment.author_id)).join(CommentVote, CommentVote.comment_id==Comment.id).filter(Comment.ghost == False, Comment.is_banned == False, Comment.deleted_utc == 0, CommentVote.vote_type==1, CommentVote.user_id==id).group_by(Comment.author_id).order_by(func.count(Comment.author_id).desc()).all() votes = Counter(dict(votes)) + Counter(dict(votes2)) users = g.db.query(User).filter(User.id.in_(votes.keys())).all() users2 = [] for user in users: users2.append((user, votes[user.id])) users = sorted(users2, key=lambda x: x[1], reverse=True) try: pos = [x[0].id for x in users].index(v.id) pos = (pos+1, users[pos][1]) except: pos = (len(users)+1, 0) return render_template("voters.html", v=v, users=users[:25], pos=pos, name='Up', name2=f'Who @{username} upvotes') @app.get("/@/downvoting") @admin_level_required(3) def downvoting(v, username): id = get_user(username).id votes = g.db.query(Submission.author_id, func.count(Submission.author_id)).join(Vote, Vote.submission_id==Submission.id).filter(Submission.ghost == False, Submission.is_banned == False, Submission.deleted_utc == 0, Vote.vote_type==-1, Vote.user_id==id).group_by(Submission.author_id).order_by(func.count(Submission.author_id).desc()).all() votes2 = g.db.query(Comment.author_id, func.count(Comment.author_id)).join(CommentVote, CommentVote.comment_id==Comment.id).filter(Comment.ghost == False, Comment.is_banned == False, Comment.deleted_utc == 0, CommentVote.vote_type==-1, CommentVote.user_id==id).group_by(Comment.author_id).order_by(func.count(Comment.author_id).desc()).all() votes = Counter(dict(votes)) + Counter(dict(votes2)) users = g.db.query(User).filter(User.id.in_(votes.keys())).all() users2 = [] for user in users: users2.append((user, votes[user.id])) users = sorted(users2, key=lambda x: x[1], reverse=True) try: pos = [x[0].id for x in users].index(v.id) pos = (pos+1, users[pos][1]) except: pos = (len(users)+1, 0) return render_template("voters.html", v=v, users=users[:25], pos=pos, name='Down', name2=f'Who @{username} downvotes') @app.post("/@/transfer_coins") @limiter.limit("1/second;30/minute;200/hour;1000/day") @is_not_permabanned def transfer_coins(v, username): abort(404) # disable entirely pending possible future use of coins receiver = g.db.query(User).filter_by(username=username).one_or_none() if receiver is None: abort(404, "That user doesn't exist.") if receiver.id != v.id: amount = request.values.get("amount", "").strip() amount = int(amount) if amount.isdigit() else None 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 log_message = f"@{v.username} has transferred {amount} coins to @{receiver.username}" send_repeatable_notification(GIFT_NOTIF_ID, log_message) receiver.coins += amount-tax v.coins -= amount send_repeatable_notification(receiver.id, f":marseycapitalistmanlet: @{v.username} has gifted you {amount-tax} coins!") g.db.add(receiver) g.db.add(v) g.db.commit() return {"message": f"{amount-tax} coins transferred!"}, 200 return {"message": "You can't transfer coins to yourself!"}, 400 @app.post("/@/transfer_bux") @limiter.limit("1/second;30/minute;200/hour;1000/day") @is_not_permabanned def transfer_bux(v, username): abort(404) # disable entirely pending possible future use of coins receiver = g.db.query(User).filter_by(username=username).one_or_none() if not receiver: abort(404, "That user doesn't exist.") if receiver.id != v.id: amount = request.values.get("amount", "").strip() amount = int(amount) if amount.isdigit() else None 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.") log_message = f"@{v.username} has transferred {amount} Marseybux to @{receiver.username}" send_repeatable_notification(GIFT_NOTIF_ID, log_message) receiver.procoins += amount v.procoins -= amount send_repeatable_notification(receiver.id, f":marseycapitalistmanlet: @{v.username} has gifted you {amount} marseybux!") g.db.add(receiver) g.db.add(v) g.db.commit() return {"message": f"{amount} marseybux transferred!"}, 200 return {"message": "You can't transfer marseybux to yourself!"}, 400 @app.get("/leaderboard") @admin_level_required(2) def leaderboard(v): users = g.db.query(User) users1 = users.order_by(User.coins.desc()).limit(25).all() sq = g.db.query(User.id, func.rank().over(order_by=User.coins.desc()).label("rank")).subquery() pos1 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1] users2 = users.order_by(User.stored_subscriber_count.desc()).limit(25).all() sq = g.db.query(User.id, func.rank().over(order_by=User.stored_subscriber_count.desc()).label("rank")).subquery() pos2 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1] users3 = users.order_by(User.post_count.desc()).limit(25).all() sq = g.db.query(User.id, func.rank().over(order_by=User.post_count.desc()).label("rank")).subquery() pos3 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1] users4 = users.order_by(User.comment_count.desc()).limit(25).all() sq = g.db.query(User.id, func.rank().over(order_by=User.comment_count.desc()).label("rank")).subquery() pos4 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1] users5 = users.order_by(User.received_award_count.desc()).limit(25).all() sq = g.db.query(User.id, func.rank().over(order_by=User.received_award_count.desc()).label("rank")).subquery() pos5 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1] users6 = None pos6 = None users7 = users.order_by(User.coins_spent.desc()).limit(25).all() sq = g.db.query(User.id, func.rank().over(order_by=User.coins_spent.desc()).label("rank")).subquery() pos7 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1] try: pos9 = [x[0].id for x in users9].index(v.id) pos9 = (pos9+1, users9[pos9][1]) except: pos9 = (len(users9)+1, 0) users10 = users.order_by(User.truecoins.desc()).limit(25).all() sq = g.db.query(User.id, func.rank().over(order_by=User.truecoins.desc()).label("rank")).subquery() pos10 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1] sq = g.db.query(Badge.user_id, func.count(Badge.user_id).label("count"), func.rank().over(order_by=func.count(Badge.user_id).desc()).label("rank")).group_by(Badge.user_id).subquery() users11 = g.db.query(User, sq.c.count).join(sq, User.id==sq.c.user_id).order_by(sq.c.count.desc()) pos11 = g.db.query(User.id, sq.c.rank, sq.c.count).join(sq, User.id==sq.c.user_id).filter(User.id == v.id).one_or_none() if pos11: pos11 = (pos11[1],pos11[2]) else: pos11 = (users11.count()+1, 0) users11 = users11.limit(25).all() if pos11[1] < 25 and v not in (x[0] for x in users11): pos11 = (26, pos11[1]) users12 = None pos12 = None try: pos13 = [x[0].id for x in users13].index(v.id) pos13 = (pos13+1, users13[pos13][1]) except: pos13 = (len(users13)+1, 0) users14 = users.order_by(User.winnings.desc()).limit(25).all() sq = g.db.query(User.id, func.rank().over(order_by=User.winnings.desc()).label("rank")).subquery() pos14 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1] users15 = users.order_by(User.winnings).limit(25).all() sq = g.db.query(User.id, func.rank().over(order_by=User.winnings).label("rank")).subquery() pos15 = g.db.query(sq.c.id, sq.c.rank).filter(sq.c.id == v.id).limit(1).one()[1] return render_template("leaderboard.html", v=v, users1=users1, pos1=pos1, users2=users2, pos2=pos2, users3=users3, pos3=pos3, users4=users4, pos4=pos4, users5=users5, pos5=pos5, users6=users6, pos6=pos6, users7=users7, pos7=pos7, users9=users9_25, pos9=pos9, users10=users10, pos10=pos10, users11=users11, pos11=pos11, users12=users12, pos12=pos12, users13=users13_25, pos13=pos13, users14=users14, pos14=pos14, users15=users15, pos15=pos15) @app.get("/@/css") def get_css(username): user = get_user(username) resp=make_response(user.css or "") resp.headers.add("Content-Type", "text/css") return resp @app.get("/@/profilecss") def get_profilecss(username): user = get_user(username) if user.profilecss: profilecss = user.profilecss else: profilecss = "" resp=make_response(profilecss) resp.headers.add("Content-Type", "text/css") return resp @app.get("/@/song") def usersong(username): user = get_user(username) if user.song: return redirect(f"/song/{user.song}.mp3") else: abort(404) @app.get("/song/") @app.get("/static/song/") def song(song): resp = make_response(send_from_directory('/songs', song)) resp.headers.remove("Cache-Control") resp.headers.add("Cache-Control", "public, max-age=3153600") return resp @app.post("/subscribe/") @limiter.limit("1/second;30/minute;200/hour;1000/day") @auth_required def subscribe(v, post_id): new_sub = Subscription(user_id=v.id, submission_id=post_id) g.db.add(new_sub) g.db.commit() return {"message": "Post subscribed!"} @app.post("/unsubscribe/") @limiter.limit("1/second;30/minute;200/hour;1000/day") @auth_required def unsubscribe(v, post_id): sub=g.db.query(Subscription).filter_by(user_id=v.id, submission_id=post_id).one_or_none() if sub: g.db.delete(sub) g.db.commit() return {"message": "Post unsubscribed!"} @app.get("/report_bugs") @auth_required def reportbugs(v): return redirect(f'/post/{BUG_THREAD}') @app.post("/@/message") @limiter.limit("1/second;10/minute;20/hour;50/day") @auth_required def message2(v, username): if v.is_suspended_permanently: abort(403, "You have been permabanned and cannot send messages; " + \ "contact modmail if you think this decision was incorrect.") user = get_user(username, v=v) if hasattr(user, 'is_blocking') and user.is_blocking: abort(403, "You're blocking this user.") if v.admin_level <= 1 and hasattr(user, 'is_blocked') and user.is_blocked: abort(403, "This user is blocking you.") message = request.values.get("message", "").strip()[:10000].strip() if not message: abort(400, "Message is empty!") body_html = sanitize(message) existing = g.db.query(Comment.id).filter(Comment.author_id == v.id, Comment.sentto == user.id, Comment.body_html == body_html, ).one_or_none() if existing: abort(403, "Message already exists.") c = Comment(author_id=v.id, parent_submission=None, level=1, sentto=user.id, body_html=body_html ) g.db.add(c) g.db.flush() c.top_comment_id = c.id notif = g.db.query(Notification).filter_by(comment_id=c.id, user_id=user.id).one_or_none() if not notif: notif = Notification(comment_id=c.id, user_id=user.id) g.db.add(notif) g.db.commit() if PUSHER_ID != 'blahblahblah' and not v.shadowbanned: if len(message) > 500: notifbody = message[:500] + '...' else: notifbody = message try: gevent.spawn(pusher_thread2, f'{request.host}{user.id}', notifbody, v.username) except: pass return {"message": "Message sent!"} @app.post("/reply") @limiter.limit("1/second;6/minute;50/hour;200/day") @auth_required def messagereply(v): message = request.values.get("body", "").strip()[:10000].strip() if not message and not request.files.get("file"): abort(400, "Message is empty!") id = int(request.values.get("parent_id")) parent = get_comment(id, v=v) user_id = parent.author.id if parent.sentto == 2: user_id = None elif v.id == user_id: user_id = parent.sentto body_html = sanitize(message) if parent.sentto == 2 and request.files.get("file") and request.headers.get("cf-ipcountry") != "T1": file=request.files["file"] if file.content_type.startswith('image/'): name = f'/images/{time.time()}'.replace('.','') + '.webp' file.save(name) url = process_image(name) body_html += f'' elif file.content_type.startswith('video/'): file.save("video.mp4") with open("video.mp4", 'rb') as f: try: req = requests.request("POST", "https://api.imgur.com/3/upload", headers={'Authorization': f'Client-ID {IMGUR_KEY}'}, files=[('video', f)], timeout=5).json()['data'] except requests.Timeout: abort(500, "Video upload timed out, please try again!") try: url = req['link'] except: error = req['error'] if error == 'File exceeds max duration': error += ' (60 seconds)' abort(400, error) if url.endswith('.'): url += 'mp4' body_html += f"

{url}

" else: abort(400, "Image/Video files only") c = Comment(author_id=v.id, parent_submission=None, parent_comment_id=id, top_comment_id=parent.top_comment_id, level=parent.level + 1, sentto=user_id, body_html=body_html, ) g.db.add(c) g.db.flush() if user_id and user_id != v.id and user_id != 2: notif = g.db.query(Notification).filter_by(comment_id=c.id, user_id=user_id).one_or_none() if not notif: notif = Notification(comment_id=c.id, user_id=user_id) g.db.add(notif) ids = [c.top_comment.id] + [x.id for x in c.top_comment.replies_ignoring_shadowbans] notifications = g.db.query(Notification).filter(Notification.comment_id.in_(ids), Notification.user_id == user_id) for n in notifications: g.db.delete(n) if PUSHER_ID != 'blahblahblah' and not v.shadowbanned: if len(message) > 500: notifbody = message[:500] + '...' else: notifbody = message beams_client.publish_to_interests( interests=[f'{request.host}{user_id}'], publish_body={ 'web': { 'notification': { 'title': f'New message from @{v.username}', 'body': notifbody, 'deep_link': f'{SITE_FULL}/notifications?messages=true', 'icon': SITE_FULL + assetcache_path(f'images/{SITE_ID}/icon.webp'), } }, 'fcm': { 'notification': { 'title': f'New message from @{v.username}', 'body': notifbody, }, 'data': { 'url': '/notifications?messages=true', } } }, ) if c.top_comment.sentto == 2: admins = g.db.query(User).filter(User.admin_level > 2, User.id != v.id).all() for admin in admins: notif = Notification(comment_id=c.id, user_id=admin.id) g.db.add(notif) ids = [c.top_comment.id] + [x.id for x in c.top_comment.replies_ignoring_shadowbans] notifications = g.db.query(Notification).filter(Notification.comment_id.in_(ids)) for n in notifications: g.db.delete(n) g.db.commit() return {"comment": render_template("comments.html", v=v, comments=[c], ajax=True)} @app.get("/2faqr/") @auth_required def mfa_qr(secret, v): x = pyotp.TOTP(secret) qr = qrcode.QRCode( error_correction=qrcode.constants.ERROR_CORRECT_L ) qr.add_data(x.provisioning_uri(v.username, issuer_name=app.config["SITE_TITLE"])) img = qr.make_image(fill_color="#000000", back_color="white") mem = io.BytesIO() img.save(mem, format="PNG") mem.seek(0, 0) try: f = send_file(mem, mimetype="image/png", as_attachment=False) except: print('/2faqr/', flush=True) abort(404) return f @app.get("/is_available/") def api_is_available(name): name=name.strip() if len(name)<3 or len(name)>25: return {name:False} name2 = sql_ilike_clean(name) x= g.db.query(User).filter( or_( User.username.ilike(name2), User.original_username.ilike(name2) ) ).one_or_none() if x: return {name: False} else: return {name: True} @app.get("/id/") @auth_desired def user_id(v, id): user = get_account(id) return redirect(user.url) @app.get("/u/") @auth_desired def redditor_moment_redirect(v, username): return redirect(f"/@{username}") @app.get("/@/followers") @auth_desired def followers(v, username): u = get_user(username, v=v) users = g.db.query(User).join(Follow, Follow.target_id == u.id).filter(Follow.user_id == User.id).order_by(Follow.created_utc).all() return render_template("followers.html", v=v, u=u, users=users) @app.get("/@/following") @auth_desired def following(v, username): u = get_user(username, v=v) users = g.db.query(User).join(Follow, Follow.user_id == u.id).filter(Follow.target_id == User.id).order_by(Follow.created_utc).all() return render_template("following.html", v=v, u=u, users=users) @app.get("/views") @auth_required def visitors(v): if v.admin_level < 2 and not v.patron: return render_template("errors/patron.html", v=v) viewers=sorted(v.viewers, key = lambda x: x.last_view_utc, reverse=True) return render_template("viewers.html", v=v, viewers=viewers) @app.get("/@") @auth_desired def u_username(username, v=None): u = get_user(username, v=v) if username != u.username: return redirect(SITE_FULL + request.full_path.replace(username, u.username)[:-1]) if u.reserved: if request.headers.get("Authorization") or request.headers.get("xhr"): abort(403, f"That username is reserved for: {u.reserved}") return render_template("userpage_reserved.html", u=u, v=v) if v and v.id != u.id and (u.patron or u.admin_level > 1): view = g.db.query(ViewerRelationship).filter_by(viewer_id=v.id, user_id=u.id).one_or_none() if view: view.last_view_utc = int(time.time()) else: view = ViewerRelationship(viewer_id=v.id, user_id=u.id) g.db.add(view) g.db.commit() if u.is_private and (not v or (v.id != u.id and v.admin_level < 2)): if request.headers.get("Authorization") or request.headers.get("xhr"): abort(403, "That 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"): abort(403, f"You are blocking @{u.username}.") return render_template("userpage_blocking.html", u=u, v=v) sort = request.values.get("sort", "new") t = request.values.get("t", "all") try: page = max(int(request.values.get("page", 1)), 1) except: page = 1 ids = u.userpagelisting(site=SITE, v=v, page=page, sort=sort, t=t) next_exists = (len(ids) > 25) ids = ids[:25] if page == 1: sticky = [] sticky = g.db.query(Submission).filter_by(is_pinned=True, author_id=u.id).all() if sticky: for p in sticky: ids = [p.id] + ids listing = get_posts(ids, v=v) if u.unban_utc: if request.headers.get("Authorization"): {"data": [x.json for x in listing]} return render_template("userpage.html", unban=u.unban_string, u=u, v=v, listing=listing, page=page, sort=sort, t=t, next_exists=next_exists, is_following=(v and u.has_follower(v))) if request.headers.get("Authorization"): return {"data": [x.json for x in listing]} return render_template("userpage.html", u=u, v=v, listing=listing, page=page, sort=sort, t=t, next_exists=next_exists, is_following=(v and u.has_follower(v))) @app.get("/@/comments") @auth_desired def u_username_comments(username, v=None): user = get_user(username, v=v) if username != user.username: return redirect(f'/@{user.username}/comments') u = user if u.reserved: if request.headers.get("Authorization") or request.headers.get("xhr"): abort(403, f"That username is reserved for: {u.reserved}") return render_template("userpage_reserved.html", u=u, v=v) if u.is_private and (not v or (v.id != u.id and v.admin_level < 2)): if request.headers.get("Authorization") or request.headers.get("xhr"): abort(403, "That 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"): 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) except: page = 1 sort=request.values.get("sort","new") t=request.values.get("t","all") comments = g.db.query(Comment.id).filter(Comment.author_id == u.id, Comment.parent_submission != None) if not v or (v.id != u.id and v.admin_level < 2): comments = comments.filter( Comment.deleted_utc == 0, Comment.is_banned == False, Comment.ghost == False, (Comment.filter_state != 'filtered') & (Comment.filter_state != 'removed') ) now = int(time.time()) if t == 'hour': cutoff = now - 3600 elif t == 'day': cutoff = now - 86400 elif t == 'week': cutoff = now - 604800 elif t == 'month': cutoff = now - 2592000 elif t == 'year': cutoff = now - 31536000 else: cutoff = 0 comments = comments.filter(Comment.created_utc >= cutoff) if sort == "new": comments = comments.order_by(Comment.created_utc.desc()) elif sort == "old": comments = comments.order_by(Comment.created_utc) elif sort == "controversial": comments = comments.order_by((Comment.upvotes+1)/(Comment.downvotes+1) + (Comment.downvotes+1)/(Comment.upvotes+1), Comment.downvotes.desc()) elif sort == "top": comments = comments.order_by(Comment.downvotes - Comment.upvotes) elif sort == "bottom": comments = comments.order_by(Comment.upvotes - Comment.downvotes) comments = comments.offset(25 * (page - 1)).limit(26).all() ids = [x.id for x in comments] next_exists = (len(ids) > 25) ids = ids[:25] listing = get_comments(ids, v=v) is_following = (v and user.has_follower(v)) if request.headers.get("Authorization"): return {"data": [c.json for c in listing]} return render_template("userpage_comments.html", u=user, v=v, listing=listing, page=page, sort=sort, t=t,next_exists=next_exists, is_following=is_following, standalone=True) @app.get("/@/info") @auth_required def u_username_info(username, v=None): user=get_user(username, v=v) if hasattr(user, 'is_blocking') and user.is_blocking: abort(401, "You're blocking this user.") elif hasattr(user, 'is_blocked') and user.is_blocked: abort(403, "This user is blocking you.") return user.json @app.get("//info") @auth_required def u_user_id_info(id, v=None): user=get_account(id, v=v) if hasattr(user, 'is_blocking') and user.is_blocking: abort(401, "You're blocking this user.") elif hasattr(user, 'is_blocked') and user.is_blocked: abort(403, "This user is blocking you.") return user.json @app.post("/follow/") @limiter.limit("1/second;30/minute;200/hour;1000/day") @auth_required def follow_user(username, v): target = get_user(username) if target.id==v.id: abort(400, "You can't follow yourself!") if g.db.query(Follow).filter_by(user_id=v.id, target_id=target.id).one_or_none(): return {"message": "User followed!"} new_follow = Follow(user_id=v.id, target_id=target.id) g.db.add(new_follow) g.db.flush() target.stored_subscriber_count = g.db.query(Follow.target_id).filter_by(target_id=target.id).count() g.db.add(target) send_notification(target.id, f"@{v.username} has followed you!") g.db.commit() return {"message": "User followed!"} @app.post("/unfollow/") @limiter.limit("1/second;30/minute;200/hour;1000/day") @auth_required def unfollow_user(username, v): target = get_user(username) follow = g.db.query(Follow).filter_by(user_id=v.id, target_id=target.id).one_or_none() if follow: g.db.delete(follow) g.db.flush() target.stored_subscriber_count = g.db.query(Follow.target_id).filter_by(target_id=target.id).count() g.db.add(target) send_notification(target.id, f"@{v.username} has unfollowed you!") g.db.commit() return {"message": "User unfollowed!"} @app.post("/remove_follow/") @limiter.limit("1/second;30/minute;200/hour;1000/day") @auth_required def remove_follow(username, v): target = get_user(username) follow = g.db.query(Follow).filter_by(user_id=target.id, target_id=v.id).one_or_none() if not follow: return {"message": "Follower removed!"} g.db.delete(follow) g.db.flush() v.stored_subscriber_count = g.db.query(Follow.target_id).filter_by(target_id=v.id).count() g.db.add(v) send_repeatable_notification(target.id, f"@{v.username} has removed your follow!") g.db.commit() return {"message": "Follower removed!"} from urllib.parse import urlparse import re @app.get("/pp/") @app.get("/uid//pic") @app.get("/uid//pic/profile") @limiter.exempt @auth_desired def user_profile_uid(v, id): try: id = int(id) except: try: id = int(id, 36) except: abort(404) name = f"/pp/{id}" path = cache.get(name) tout = 5 * 60 # 5 min # if the path isn't cached then make it if not path: user = get_account(id) path = urlparse(user.profile_url).path cache.set(name,path,timeout=tout) # if not found, search relative to the root if not os.path.exists(path): path = os.path.join(app.root_path,path.lstrip('/')) cache.set(name,path,timeout=tout) # if not found, fail if not os.path.exists(path): cache.set(name,None) abort(404) return send_file(path) @app.get("/@/pic") @limiter.exempt @auth_required def user_profile_name(v, username): name = f"/@{username}/pic" path = cache.get(name) tout = 5 * 60 # 5 min # if the path isn't cached then make it if not path: user = get_user(username) path = urlparse(user.profile_url).path cache.set(name,path,timeout=tout) # if not found, search relative to the root if not os.path.exists(path): path = os.path.join(app.root_path,path.lstrip('/')) cache.set(name,path,timeout=tout) # if not found, fail if not os.path.exists(path): cache.set(name,None) abort(404) return send_file(path) @app.get("/@/saved/posts") @auth_required def saved_posts(v, username): page=int(request.values.get("page",1)) ids=v.saved_idlist(page=page) next_exists=len(ids)>25 ids=ids[:25] listing = get_posts(ids, v=v) if request.headers.get("Authorization"): return {"data": [x.json for x in listing]} return render_template("userpage.html", u=v, v=v, listing=listing, page=page, next_exists=next_exists, ) @app.get("/@/saved/comments") @auth_required def saved_comments(v, username): page=int(request.values.get("page",1)) ids=v.saved_comment_idlist(page=page) next_exists=len(ids) > 25 ids=ids[:25] listing = get_comments(ids, v=v) if request.headers.get("Authorization"): return {"data": [x.json for x in listing]} return render_template("userpage_comments.html", u=v, v=v, listing=listing, page=page, next_exists=next_exists, standalone=True) @app.post("/fp/") @auth_required def fp(v, fp): v.fp = fp users = g.db.query(User).filter(User.fp == fp, User.id != v.id).all() if users: print(f'{v.username}: fp {v.fp}') if v.email and v.is_activated: alts = g.db.query(User).filter(User.email == v.email, User.is_activated, User.id != v.id).all() if alts: print(f'{v.username}: email {v.email}') users += alts for u in users: li = [v.id, u.id] existing = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).one_or_none() if existing: continue new_alt = Alt(user1=v.id, user2=u.id) g.db.add(new_alt) g.db.flush() print(v.username + ' + ' + u.username) g.db.add(v) g.db.commit() return '', 204