Ejemplo n.º 1
0
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)
    }
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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!"})
Ejemplo n.º 4
0
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)
    }
Ejemplo n.º 5
0
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!"})