def submit_post(v): title = request.form.get("title", "").lstrip().rstrip() title = title.lstrip().rstrip() title = title.replace("\n", "") title = title.replace("\r", "") title = title.replace("\t", "") url = request.form.get("url", "") board = get_guild(request.form.get('board', 'general'), graceful=True) if not board: board = get_guild('general') if not title: return { "html": lambda: (render_template("submit.html", v=v, error="Please enter a better title.", title=title, url=url, body=request.form.get("body", ""), b=board), 400), "api": lambda: ({ "error": "Please enter a better title" }, 400) } # if len(title)<10: # return render_template("submit.html", # v=v, # error="Please enter a better title.", # title=title, # url=url, # body=request.form.get("body",""), # b=board # ) elif len(title) > 500: return { "html": lambda: (render_template("submit.html", v=v, error="500 character limit for titles.", title=title[0:500], url=url, body=request.form.get("body", ""), b=board), 400), "api": lambda: ({ "error": "500 character limit for titles" }, 400) } parsed_url = urlparse(url) if not (parsed_url.scheme and parsed_url.netloc) and not request.form.get( "body") and not request.files.get("file", None): return { "html": lambda: (render_template("submit.html", v=v, error="Please enter a url or some text.", title=title, url=url, body=request.form.get("body", ""), b=board), 400), "api": lambda: ({ "error": "`url` or `body` parameter required." }, 400) } # sanitize title title = bleach.clean(title, tags=[]) # Force https for submitted urls if request.form.get("url"): new_url = ParseResult(scheme="https", netloc=parsed_url.netloc, path=parsed_url.path, params=parsed_url.params, query=parsed_url.query, fragment=parsed_url.fragment) url = urlunparse(new_url) else: url = "" body = request.form.get("body", "") # check for duplicate dup = g.db.query(Submission).join(Submission.submission_aux).filter( Submission.author_id == v.id, Submission.deleted_utc == 0, Submission.board_id == board.id, SubmissionAux.title == title, SubmissionAux.url == url, SubmissionAux.body == body).first() if dup: return redirect(dup.permalink) # check for domain specific rules parsed_url = urlparse(url) domain = parsed_url.netloc # check ban status domain_obj = get_domain(domain) if domain_obj: if not domain_obj.can_submit: if domain_obj.reason == 4: v.ban(days=30, reason="Digitally malicious content") elif domain_obj.reason == 7: v.ban(reason="Sexualizing minors") return { "html": lambda: (render_template("submit.html", v=v, error=BAN_REASONS[domain_obj.reason], title=title, url=url, body=request.form.get("body", ""), b=board), 400), "api": lambda: ({ "error": BAN_REASONS[domain_obj.reason] }, 400) } # check for embeds if domain_obj.embed_function: try: embed = eval(domain_obj.embed_function)(url) except BaseException: embed = "" else: embed = "" else: embed = "" # board board_name = request.form.get("board", "general") board_name = board_name.lstrip("+") board_name = board_name.rstrip() board = get_guild(board_name, graceful=True) if not board: return { "html": lambda: (render_template("submit.html", v=v, error=f"Please enter a Guild to submit to.", title=title, url=url, body=request.form.get("body", ""), b=None), 403), "api": lambda: (jsonify( {"error": f"403 Forbidden - +{board.name} has been banned."})) } if board.is_banned: return { "html": lambda: (render_template("submit.html", v=v, error=f"+{board.name} has been banned.", title=title, url=url, body=request.form.get("body", ""), b=None), 403), "api": lambda: (jsonify( {"error": f"403 Forbidden - +{board.name} has been banned."})) } if board.has_ban(v): return { "html": lambda: (render_template("submit.html", v=v, error=f"You are exiled from +{board.name}.", title=title, url=url, body=request.form.get("body", ""), b=None), 403), "api": lambda: (jsonify({ "error": f"403 Not Authorized - You are exiled from +{board.name}" }), 403) } if (board.restricted_posting or board.is_private) and not (board.can_submit(v)): return { "html": lambda: (render_template( "submit.html", v=v, error= f"You are not an approved contributor for +{board.name}.", title=title, url=url, body=request.form.get("body", ""), b=None), 403), "api": lambda: (jsonify({ "error": f"403 Not Authorized - You are not an approved contributor for +{board.name}" }), 403) } if board.disallowbots and request.headers.get("X-User-Type") == "Bot": return { "api": lambda: (jsonify({ "error": f"403 Not Authorized - +{board.name} disallows bots from posting and commenting!" }), 403) } # similarity check now = int(time.time()) cutoff = now - 60 * 60 * 24 similar_posts = g.db.query(Submission).options(lazyload('*')).join( Submission.submission_aux ).filter( #or_( # and_( Submission.author_id == v.id, SubmissionAux.title.op('<->')(title) < app.config["SPAM_SIMILARITY_THRESHOLD"], Submission.created_utc > cutoff # ), # and_( # SubmissionAux.title.op('<->')(title) < app.config["SPAM_SIMILARITY_THRESHOLD"]/2, # Submission.created_utc > cutoff # ) #) ).all() if url: similar_urls = g.db.query(Submission).options(lazyload('*')).join( Submission.submission_aux ).filter( #or_( # and_( Submission.author_id == v.id, SubmissionAux.url.op('<->')(url) < app.config["SPAM_URL_SIMILARITY_THRESHOLD"], Submission.created_utc > cutoff # ), # and_( # SubmissionAux.url.op('<->')(url) < app.config["SPAM_URL_SIMILARITY_THRESHOLD"]/2, # Submission.created_utc > cutoff # ) #) ).all() else: similar_urls = [] threshold = app.config["SPAM_SIMILAR_COUNT_THRESHOLD"] if v.age >= (60 * 60 * 24 * 7): threshold *= 3 elif v.age >= (60 * 60 * 24): threshold *= 2 if max(len(similar_urls), len(similar_posts)) >= threshold: text = "Your Ruqqus account has been suspended for 1 day for the following reason:\n\n> Too much spam!" send_notification(v, text) v.ban(reason="Spamming.", days=1) for alt in v.alts: if not alt.is_suspended: alt.ban(reason="Spamming.", days=1) for post in similar_posts + similar_urls: post.is_banned = True post.is_pinned = False post.ban_reason = "Automatic spam removal. This happened because the post's creator submitted too much similar content too quickly." g.db.add(post) ma = ModAction(user_id=1, target_submission_id=post.id, kind="ban_post", board_id=post.board_id, note="spam") g.db.add(ma) g.db.commit() return redirect("/notifications") # catch too-long body if len(str(body)) > 10000: return { "html": lambda: (render_template("submit.html", v=v, error="10000 character limit for text body.", title=title, url=url, body=request.form.get("body", ""), b=board), 400), "api": lambda: ({ "error": "10000 character limit for text body." }, 400) } if len(url) > 2048: return { "html": lambda: (render_template("submit.html", v=v, error="2048 character limit for URLs.", title=title, url=url, body=request.form.get("body", ""), b=board), 400), "api": lambda: ({ "error": "2048 character limit for URLs." }, 400) } # render text body = preprocess(body) with CustomRenderer() as renderer: body_md = renderer.render(mistletoe.Document(body)) body_html = sanitize(body_md, linkgen=True) # Run safety filter bans = filter_comment_html(body_html) if bans: ban = bans[0] reason = f"Remove the {ban.domain} link from your post and try again." if ban.reason: reason += f" {ban.reason_text}" #auto ban for digitally malicious content if any([x.reason == 4 for x in bans]): v.ban(days=30, reason="Digitally malicious content is not allowed.") abort(403) return { "html": lambda: (render_template("submit.html", v=v, error=reason, title=title, url=url, body=request.form.get("body", ""), b=board), 403), "api": lambda: ({ "error": reason }, 403) } # check spam soup = BeautifulSoup(body_html, features="html.parser") links = [x['href'] for x in soup.find_all('a') if x.get('href')] if url: links = [url] + links for link in links: parse_link = urlparse(link) check_url = ParseResult(scheme="https", netloc=parse_link.netloc, path=parse_link.path, params=parse_link.params, query=parse_link.query, fragment='') check_url = urlunparse(check_url) badlink = g.db.query(BadLink).filter( literal(check_url).contains(BadLink.link)).first() if badlink: if badlink.autoban: text = "Your Ruqqus account has been suspended for 1 day for the following reason:\n\n> Too much spam!" send_notification(v, text) v.ban(days=1, reason="spam") return redirect('/notifications') else: return { "html": lambda: (render_template( "submit.html", v=v, error= f"The link `{badlink.link}` is not allowed. Reason: {badlink.reason}.", title=title, url=url, body=request.form.get("body", ""), b=board), 400), "api": lambda: ({ "error": f"The link `{badlink.link}` is not allowed. Reason: {badlink.reason}" }, 400) } # check for embeddable video domain = parsed_url.netloc if url: repost = g.db.query(Submission).join(Submission.submission_aux).filter( SubmissionAux.url.ilike(url), Submission.board_id == board.id, Submission.deleted_utc == 0, Submission.is_banned == False).order_by( Submission.id.asc()).first() else: repost = None if repost and request.values.get("no_repost"): return redirect(repost.permalink) if request.files.get('file') and not v.can_submit_image: abort(403) # offensive is_offensive = False for x in g.db.query(BadWord).all(): if (body and x.check(body)) or x.check(title): is_offensive = True break new_post = Submission(author_id=v.id, domain_ref=domain_obj.id if domain_obj else None, board_id=board.id, original_board_id=board.id, over_18=(bool(request.form.get("over_18", "")) or board.over_18), post_public=not board.is_private, repost_id=repost.id if repost else None, is_offensive=is_offensive, app_id=v.client.application.id if v.client else None, creation_region=request.headers.get("cf-ipcountry"), is_bot=request.headers.get("X-User-Type", "").lower() == "bot") g.db.add(new_post) g.db.flush() new_post_aux = SubmissionAux(id=new_post.id, url=url, body=body, body_html=body_html, embed_url=embed, title=title) g.db.add(new_post_aux) g.db.flush() vote = Vote(user_id=v.id, vote_type=1, submission_id=new_post.id) g.db.add(vote) g.db.flush() g.db.refresh(new_post) # check for uploaded image if request.files.get('file'): #check file size if request.content_length > 16 * 1024 * 1024 and not v.has_premium: g.db.rollback() abort(413) file = request.files['file'] if not file.content_type.startswith('image/'): return { "html": lambda: (render_template("submit.html", v=v, error=f"Image files only.", title=title, body=request.form.get("body", ""), b=board), 400), "api": lambda: ({ "error": f"Image files only" }, 400) } name = f'post/{new_post.base36id}/{secrets.token_urlsafe(8)}' upload_file(name, file) # thumb_name=f'posts/{new_post.base36id}/thumb.png' #upload_file(name, file, resize=(375,227)) # update post data new_post.url = f'https://{BUCKET}/{name}' new_post.is_image = True new_post.domain_ref = 1 # id of i.ruqqus.com domain g.db.add(new_post) g.db.add(new_post.submission_aux) g.db.commit() #csam detection def del_function(): db = db_session() delete_file(name) new_post.is_banned = True db.add(new_post) db.commit() ma = ModAction(kind="ban_post", user_id=1, note="banned image", target_submission_id=new_post.id) db.add(ma) db.commit() db.close() csam_thread = threading.Thread(target=check_csam_url, args=(f"https://{BUCKET}/{name}", v, del_function)) csam_thread.start() g.db.commit() # spin off thumbnail generation and csam detection as new threads if (new_post.url or request.files.get('file')) and ( v.is_activated or request.headers.get('cf-ipcountry') != "T1"): new_thread = gevent.spawn(thumbnail_thread, new_post.base36id) # expire the relevant caches: front page new, board new cache.delete_memoized(frontlist) g.db.commit() cache.delete_memoized(Board.idlist, board, sort="new") # queue up notifications for username mentions notify_users = set() soup = BeautifulSoup(body_html, features="html.parser") for mention in soup.find_all("a", href=re.compile("^/@(\w+)"), limit=3): username = mention["href"].split("@")[1] user = g.db.query(User).filter_by(username=username).first() if user and not v.any_block_exists(user) and user.id != v.id: notify_users.add(user.id) for x in notify_users: send_notification( x, f"@{v.username} has mentioned you: https://ruqqus.com{new_post.permalink}" ) # print(f"Content Event: @{new_post.author.username} post # {new_post.base36id}") #Bell notifs board_uids = g.db.query(Subscription.user_id).options( lazyload('*')).filter( Subscription.board_id == new_post.board_id, Subscription.is_active == True, Subscription.get_notifs == True, Subscription.user_id != v.id, Subscription.user_id.notin_( g.db.query( UserBlock.user_id).filter_by(target_id=v.id).subquery())) follow_uids = g.db.query(Follow.user_id).options(lazyload('*')).filter( Follow.target_id == v.id, Follow.get_notifs == True, Follow.user_id != v.id, Follow.user_id.notin_( g.db.query( UserBlock.user_id).filter_by(target_id=v.id).subquery()), Follow.user_id.notin_( g.db.query(UserBlock.target_id).filter_by( user_id=v.id).subquery())).join(Follow.target).filter( User.is_private == False, User.is_nofollow == False, ) if not new_post.is_public: contribs = g.db.query(ContributorRelationship).filter_by( board_id=new_post.board_id, is_active=True).subquery() mods = g.db.query(ModRelationship).filter_by( board_id=new_post.board_id, accepted=True).subquery() board_uids = board.uids.join( contribs, contribs.c.user_id == Subscription.user_id, isouter=True).join(mods, mods.c.user_id == Subscription.user_id, isouter=True).filter( or_(mods.c.id != None, contribs.c.id != None)) follow_uids = follow_uids.join(contribs, contribs.c.user_id == Follow.user_id, isouter=True).join( mods, mods.c.user_id == Follow.user_id, isouter=True).filter( or_(mods.c.id != None, contribs.c.id != None)) uids = list( set([x[0] for x in board_uids.all()] + [x[0] for x in follow_uids.all()])) for uid in uids: new_notif = Notification(user_id=uid, submission_id=new_post.id) g.db.add(new_notif) g.db.commit() return { "html": lambda: redirect(new_post.permalink), "api": lambda: jsonify(new_post.json) }
def edit_post(pid, v): p = get_post(pid) if not p.author_id == v.id: abort(403) if p.is_banned: abort(403) if p.board.has_ban(v): abort(403) body = request.form.get("body", "") body = preprocess(body) with CustomRenderer() as renderer: body_md = renderer.render(mistletoe.Document(body)) body_html = sanitize(body_md, linkgen=True) # Run safety filter bans = filter_comment_html(body_html) if bans: ban = bans[0] reason = f"Remove the {ban.domain} link from your post and try again." if ban.reason: reason += f" {ban.reason_text}" #auto ban for digitally malicious content if any([x.reason == 4 for x in bans]): v.ban(days=30, reason="Digitally malicious content is not allowed.") abort(403) return {"error": reason}, 403 # check spam soup = BeautifulSoup(body_html, features="html.parser") links = [x['href'] for x in soup.find_all('a') if x.get('href')] for link in links: parse_link = urlparse(link) check_url = ParseResult(scheme="https", netloc=parse_link.netloc, path=parse_link.path, params=parse_link.params, query=parse_link.query, fragment='') check_url = urlunparse(check_url) badlink = g.db.query(BadLink).filter( literal(check_url).contains(BadLink.link)).first() if badlink: if badlink.autoban: text = "Your Ruqqus account has been suspended for 1 day for the following reason:\n\n> Too much spam!" send_notification(v, text) v.ban(days=1, reason="spam") return redirect('/notifications') else: return { "error": f"The link `{badlink.link}` is not allowed. Reason: {badlink.reason}" } p.body = body p.body_html = body_html p.edited_utc = int(time.time()) # offensive p.is_offensive = False for x in g.db.query(BadWord).all(): if (p.body and x.check(p.body)) or x.check(p.title): p.is_offensive = True break g.db.add(p) return redirect(p.permalink)
def gift_comment_pid(cid, v): comment = get_comment(cid, v=v) if comment.author_id == v.id: return jsonify({"error": "You can't give awards to yourself."}), 403 if comment.deleted_utc > 0: return jsonify({"error": "You can't give awards to deleted posts"}), 403 if comment.is_banned: return jsonify({"error": "You can't give awards to removed posts"}), 403 if comment.author.is_deleted: return jsonify({"error": "You can't give awards to deleted accounts"}), 403 if comment.author.is_banned and not comment.author.unban_utc: return jsonify({"error": "You can't give awards to banned accounts"}), 403 u = get_user(comment.author.username, v=v) if u.is_blocking: return jsonify( {"error": "You can't give awards to someone you're blocking."}), 403 if u.is_blocked: return jsonify( {"error": "You can't give awards to someone that's blocking you."}), 403 coins = int(request.args.get("coins", 1)) if not coins: return jsonify({"error": "You need to actually give coins."}), 400 if coins < 0: return jsonify({ "error": "What are you doing, trying to *charge* someone coins?." }), 400 v = g.db.query(User).with_for_update().options( lazyload('*')).filter_by(id=v.id).first() u = g.db.query(User).with_for_update().options( lazyload('*')).filter_by(id=u.id).first() if not v.coin_balance >= coins: return jsonify({"error": "You don't have that many coins to give!"}), 403 v.coin_balance -= coins u.coin_balance += coins g.db.add(v) g.db.add(u) g.db.flush() if v.coin_balance < 0: g.db.rollback() return jsonify({"error": "You don't have that many coins to give!"}), 403 if not g.db.query(AwardRelationship).filter_by( user_id=v.id, comment_id=comment.id).first(): text = f"Someone liked [your comment]({comment.permalink}) and has given you a Coin!\n\n" if u.premium_expires_utc < int(time.time()): text += "Your Coin has been automatically redeemed for one week of [Ruqqus Premium](/settings/premium)." else: text += "Since you already have Ruqqus Premium, the Coin has been added to your balance. You can keep it for yourself, or give it to someone else." send_notification(u, text) g.db.commit() #create record - uniqe prevents duplicates new_rel = AwardRelationship(user_id=v.id, comment_id=comment.id) try: g.db.add(new_rel) g.db.flush() except: pass g.db.commit() return jsonify({"message": "Tip Successful!"})
def submit_post(v): title = request.form.get("title", "") title = title.lstrip().rstrip() title = title.replace("\n", "") title = title.replace("\r", "") title = title.replace("\t", "") url = request.form.get("url", "") board = get_guild(request.form.get('board', 'general'), graceful=True) if not board: board = get_guild('general') if not title: return render_template("submit.html", v=v, error="Please enter a better title.", title=title, url=url, body=request.form.get("body", ""), b=board) # if len(title)<10: # return render_template("submit.html", # v=v, # error="Please enter a better title.", # title=title, # url=url, # body=request.form.get("body",""), # b=board # ) elif len(title) > 500: return render_template("submit.html", v=v, error="500 character limit for titles.", title=title[0:500], url=url, body=request.form.get("body", ""), b=board) parsed_url = urlparse(url) if not (parsed_url.scheme and parsed_url.netloc) and not request.form.get( "body") and not request.files.get("file", None): return render_template("submit.html", v=v, error="Please enter a URL or some text.", title=title, url=url, body=request.form.get("body", ""), b=board) #sanitize title title = bleach.clean(title) #Force https for submitted urls if request.form.get("url"): new_url = ParseResult(scheme="https", netloc=parsed_url.netloc, path=parsed_url.path, params=parsed_url.params, query=parsed_url.query, fragment=parsed_url.fragment) url = urlunparse(new_url) else: url = "" body = request.form.get("body", "") #check for duplicate dup = g.db.query(Submission).join(Submission.submission_aux).filter( Submission.author_id == v.id, Submission.is_deleted == False, Submission.board_id == board.id, SubmissionAux.title == title, SubmissionAux.url == url, SubmissionAux.body == body).first() if dup: return redirect(dup.permalink) #check for domain specific rules parsed_url = urlparse(url) domain = parsed_url.netloc # check ban status domain_obj = get_domain(domain) if domain_obj: if not domain_obj.can_submit: return render_template("submit.html", v=v, error=BAN_REASONS[domain_obj.reason], title=title, url=url, body=request.form.get("body", ""), b=get_guild(request.form.get( "board", "general"), graceful=True)) #check for embeds if domain_obj.embed_function: try: embed = eval(domain_obj.embed_function)(url) except: embed = "" else: embed = "" else: embed = "" #board board_name = request.form.get("board", "general") board_name = board_name.lstrip("+") board_name = board_name.rstrip() board = get_guild(board_name, graceful=True) if not board: board = get_guild('general') if board.is_banned: return render_template("submit.html", v=v, error=f"+{board.name} has been demolished.", title=title, url=url, body=request.form.get("body", ""), b=get_guild("general", graceful=True)), 403 if board.has_ban(v): return render_template("submit.html", v=v, error=f"You are exiled from +{board.name}.", title=title, url=url, body=request.form.get("body", ""), b=get_guild("general")), 403 if (board.restricted_posting or board.is_private) and not (board.can_submit(v)): return render_template( "submit.html", v=v, error=f"You are not an approved contributor for +{board.name}.", title=title, url=url, body=request.form.get("body", ""), b=get_guild(request.form.get("board", "general"), graceful=True)) #similarity check now = int(time.time()) cutoff = now - 60 * 60 * 24 similar_posts = g.db.query(Submission).options(lazyload('*')).join( Submission.submission_aux).filter( Submission.author_id == v.id, SubmissionAux.title.op('<->')(title) < app.config["SPAM_SIMILARITY_THRESHOLD"], Submission.created_utc > cutoff).all() if url: similar_urls = g.db.query(Submission).options(lazyload('*')).join( Submission.submission_aux).filter( Submission.author_id == v.id, SubmissionAux.url.op('<->')(url) < app.config["SPAM_URL_SIMILARITY_THRESHOLD"], Submission.created_utc > cutoff).all() else: similar_urls = [] threshold = app.config["SPAM_SIMILAR_COUNT_THRESHOLD"] if v.age >= (60 * 60 * 24 * 30): threshold *= 4 elif v.age >= (60 * 60 * 24 * 7): threshold *= 3 elif v.age >= (60 * 60 * 24): threshold *= 2 if max(len(similar_urls), len(similar_posts)) >= threshold: text = "Your Ruqqus account has been suspended for 1 day for the following reason:\n\n> Too much spam!" send_notification(v, text) v.ban(reason="Spamming.", include_alts=True, days=1) for post in similar_posts + similar_urls: post.is_banned = True post.ban_reason = "Automatic spam removal. This happened because the post's creator submitted too much similar content too quickly." g.db.add(post) g.db.commit() return redirect("/notifications") #catch too-long body if len(str(body)) > 10000: return render_template("submit.html", v=v, error="10000 character limit for text body", title=title, text=str(body)[0:10000], url=url, b=get_guild(request.form.get( "board", "general"), graceful=True)), 400 if len(url) > 2048: return render_template("submit.html", v=v, error="URLs cannot be over 2048 characters", title=title, text=body[0:2000], b=get_guild(request.form.get( "board", "general"), graceful=True)), 400 #render text with CustomRenderer() as renderer: body_md = renderer.render(mistletoe.Document(body)) body_html = sanitize(body_md, linkgen=True) ##check spam soup = BeautifulSoup(body_html, features="html.parser") links = [x['href'] for x in soup.find_all('a') if x.get('href')] if url: links = [url] + links for link in links: parse_link = urlparse(link) check_url = ParseResult(scheme="https", netloc=parse_link.netloc, path=parse_link.path, params=parse_link.params, query=parse_link.query, fragment='') check_url = urlunparse(check_url) badlink = g.db.query(BadLink).filter( literal(check_url).contains(BadLink.link)).first() if badlink: if badlink.autoban: text = "Your Ruqqus account has been suspended for 1 day for the following reason:\n\n> Too much spam!" send_notification(v, text) v.ban(days=1, reason="spam") return redirect('/notifications') else: return render_template( "submit.html", v=v, error= f"The link `{badlink.link}` is not allowed. Reason: {badlink.reason}", title=title, text=body[0:2000], b=get_guild(request.form.get("board", "general"), graceful=True)), 400 #check for embeddable video domain = parsed_url.netloc if url: repost = g.db.query(Submission).join(Submission.submission_aux).filter( SubmissionAux.url.ilike(url), Submission.board_id == board.id, Submission.is_deleted == False, Submission.is_banned == False).order_by( Submission.id.asc()).first() else: repost = None if request.files.get('file') and not v.can_submit_image: abort(403) #offensive is_offensive = False for x in g.db.query(BadWord).all(): if (body and x.check(body)) or x.check(title): is_offensive = True break new_post = Submission(author_id=v.id, domain_ref=domain_obj.id if domain_obj else None, board_id=board.id, original_board_id=board.id, over_18=(bool(request.form.get("over_18", "")) or board.over_18), post_public=not board.is_private, repost_id=repost.id if repost else None, is_offensive=is_offensive) g.db.add(new_post) g.db.flush() new_post_aux = SubmissionAux(id=new_post.id, url=url, body=body, body_html=body_html, embed_url=embed, title=title) g.db.add(new_post_aux) g.db.flush() vote = Vote(user_id=v.id, vote_type=1, submission_id=new_post.id) g.db.add(vote) g.db.flush() g.db.commit() g.db.refresh(new_post) #check for uploaded image if request.files.get('file'): file = request.files['file'] name = f'post/{new_post.base36id}/{secrets.token_urlsafe(8)}' upload_file(name, file) #thumb_name=f'posts/{new_post.base36id}/thumb.png' #upload_file(name, file, resize=(375,227)) #update post data new_post.url = f'https://{BUCKET}/{name}' new_post.is_image = True new_post.domain_ref = 1 #id of i.ruqqus.com domain g.db.add(new_post) g.db.commit() #spin off thumbnail generation and csam detection as new threads if new_post.url or request.files.get('file'): new_thread = threading.Thread(target=thumbnail_thread, args=(new_post.base36id, )) new_thread.start() csam_thread = threading.Thread(target=check_csam, args=(new_post, )) csam_thread.start() #expire the relevant caches: front page new, board new #cache.delete_memoized(frontlist, sort="new") g.db.commit() cache.delete_memoized(Board.idlist, board, sort="new") #print(f"Content Event: @{new_post.author.username} post {new_post.base36id}") return { "html": lambda: redirect(new_post.permalink), "api": lambda: jsonify(new_post.json) }
def gift_post_pid(pid, v): post = get_post(pid, v=v) if post.author_id == v.id: return jsonify({"error": "You can't give awards to yourself."}), 403 if post.is_deleted: return jsonify({"error": "You can't give awards to deleted posts"}), 403 if post.is_banned: return jsonify({"error": "You can't give awards to removed posts"}), 403 if post.author.is_deleted: return jsonify({"error": "You can't give awards to deleted accounts"}), 403 if post.author.is_banned and not post.author.unban_utc: return jsonify({"error": "You can't give awards to banned accounts"}), 403 u = get_user(post.author.username, v=v) if u.is_blocking: return jsonify( {"error": "You can't give awards to someone you're blocking."}), 403 if u.is_blocked: return jsonify( {"error": "You can't give awards to someone that's blocking you."}), 403 coins = int(request.args.get("coins", 1)) if not coins: return jsonify({"error": "You need to actually give coins."}), 400 if coins < 0: return jsonify({ "error": "What are you doing, trying to *charge* someone coins?." }), 400 if not v.coin_balance >= coins: return jsonify({"error": "You don't have that many coins to give!"}), 403 if not g.db.query(AwardRelationship).filter_by( user_id=v.id, submission_id=post.id).first(): text = f"Someone liked [your post]({post.permalink}) and has given you a Coin!\n\n" if not u.has_premium_no_renew: text += "Your Coin has been automatically redeemed for one week of [Ruqqus Premium](/settings/premium)." else: text += "Since you already have Ruqqus Premium, the Coin has been added to your balance. You can keep it for yourself, or give it to someone else." send_notification(u, text) v.coin_balance -= coins u.coin_balance += coins g.db.add(v) g.db.add(u) g.db.commit() #create record - uniqueness constraints prevent duplicate award counting new_rel = AwardRelationship(user_id=v.id, submission_id=post.id) try: g.db.add(new_rel) g.db.flush() except: pass return jsonify({"message": "Tip Successful!"})