Exemplo n.º 1
0
def run_bot(type=Type.MENTION,
            guess_and_check=True,
            reply_to_item=True,
            write_to_database=True):
    # Get the stream instance (contains session, type and data)
    s = stream.get_stream(type)
    log.info("Set up new stream")

    # Start the stream
    for message in s.praw_session.inbox.stream():
        # Mark the item as read
        message.mark_read()

        # Log the message type and id
        log.info(f"New message: {message.type}: {message.fullname}")

        # If the message is a comment_reply, ignore it
        if message.type == "comment_reply":
            continue
        # If the message is an username_mention, start summon process
        if message.type == "username_mention":
            parent = message.parent()
            i = Item(type=Type.MENTION,
                     id=parent.name,
                     subreddit=parent.subreddit,
                     author=parent.author,
                     context=message.context,
                     summoner=message.author,
                     parent_link=parent.permalink)

            # Check if the parent is a comment or submission
            if isinstance(parent, praw.models.Comment):
                i.body = parent.body
                i.parent_type = Type.COMMENT
            elif isinstance(parent, praw.models.Submission):
                i.body = get_submission_body(parent)
                i.parent_type = Type.SUBMISSION
            else:
                log.warning("Unknown parent instance")

            # Check if the item meets the criteria
            meets_criteria, result_code = check_criteria(
                item=i,
                data=s,
                history_failed=s.mentions_failed,
                history_success=s.mentions_success,
                mustBeAMP=True,
                mustBeNew=True,
                mustNotBeDisallowedSubreddit=True,
                mustNotHaveFailed=True,
                mustNotBeMine=True,
                mustNotBeOptedOut=True,
                mustNotHaveDisallowedMods=True)

            # If it meets the criteria, try to find the canonicals and make a reply
            if result_code != ResultCode.ERROR_NO_AMP:
                log.info(
                    f"{i.id} in r/{i.subreddit} is AMP, result_code={result_code.value}"
                )
                # Get the urls from the body and try to find the canonicals
                urls = get_urls(i.body)
                i.links = get_urls_info(urls, guess_and_check)

                # If a canonical was found, generate a reply, otherwise log a warning
                if any(link.canonical
                       for link in i.links) or any(link.amp_canonical
                                                   for link in i.links):
                    # Generate a reply
                    reply_text, reply_canonical_text = generate_reply(
                        stream_type=s.type,
                        np_subreddits=s.np_subreddits,
                        item_type=i.parent_type,
                        subreddit=i.subreddit,
                        links=i.links,
                        summoned_link=i.context)

                    # Send a DM if AmputatorBot can't reply because it's disallowed by a subreddit, mod or user
                    if result_code == ResultCode.ERROR_DISALLOWED_SUBREDDIT \
                            or result_code == ResultCode.ERROR_DISALLOWED_MOD \
                            or result_code == ResultCode.ERROR_USER_OPTED_OUT:

                        # Generate and send an error DM dynamically based on the error
                        subject, message = dm_generator(
                            result_code=result_code,
                            parent_link=i.parent_link,
                            parent_subreddit=i.subreddit,
                            parent_type=i.parent_type.value,
                            first_amp_url=i.links[0].url_clean,
                            canonical_text=reply_canonical_text)
                        s.praw_session.redditor(str(i.summoner)).message(
                            subject, message)
                        log.info(f"Send summoner DM of type {result_code}")

                    # Try to post the reply, send a DM to the summoner
                    elif reply_to_item:
                        try:
                            reply = parent.reply(reply_text)
                            log.info(f"Replied to {i.id} with {reply.name}")
                            update_local_data("mentions_success", i.id)
                            s.mentions_success.append(i.id)

                            # Generate and send a SUCCESS DM to the summoner
                            result_code = ResultCode.SUCCESS
                            subject, message = dm_generator(
                                result_code=result_code,
                                reply_link=reply.permalink,
                                parent_subreddit=i.subreddit,
                                parent_type=i.parent_type.value,
                                parent_link=i.parent_link,
                                first_amp_url=i.links[0].url_clean,
                                canonical_text=reply_canonical_text)
                            s.praw_session.redditor(str(i.summoner)).message(
                                subject, message)
                            log.info(f"Send summoner DM of type {result_code}")

                        except (Forbidden, Exception):
                            log.warning("Couldn't post reply!")
                            log.error(traceback.format_exc())
                            update_local_data("mentions_failed", i.id)
                            s.mentions_failed.append(i.id)

                            # Check if AmputatorBot is banned in the subreddit
                            is_banned = check_if_banned(i.subreddit)
                            if is_banned:
                                update_local_data("disallowed_subreddits",
                                                  i.subreddit)
                                s.disallowed_subreddits.append(i.subreddit)

                            # Generate and send an ERROR_REPLY_FAILED DM to the summoner
                            result_code = ResultCode.ERROR_REPLY_FAILED
                            subject, message = dm_generator(
                                result_code=result_code,
                                parent_type=i.parent_type.value,
                                parent_link=i.parent_link,
                                first_amp_url=i.links[0].url_clean,
                                canonical_text=reply_canonical_text)
                            s.praw_session.redditor(str(i.summoner)).message(
                                subject, message)
                            log.info(f"Send summoner DM of type {result_code}")

                # If no canonicals were found, log the failed attempt
                else:
                    log.warning("No canonicals found")
                    update_local_data("mentions_failed", i.id)
                    s.mentions_failed.append(i.id)

                    # Check if the domain is problematic (meaning it's raising frequent errors)
                    if any(link.domain in s.problematic_domains
                           for link in i.links):
                        result_code = ResultCode.ERROR_PROBLEMATIC_DOMAIN
                    else:
                        result_code = ResultCode.ERROR_NO_CANONICALS

                    # Generate and send an
                    subject, message = dm_generator(
                        result_code=result_code,
                        parent_type=i.parent_type.value,
                        parent_link=i.parent_link,
                        first_amp_url=i.links[0].url_clean)

                    s.praw_session.redditor(str(i.summoner)).message(
                        subject, message)

                # If write_to_database is enabled, make a new entry for every URL
                if write_to_database:
                    for link in i.links:
                        if link.is_amp:
                            add_data(session=get_engine_session(),
                                     entry_type=type.value,
                                     handled_utc=datetime.now().strftime(
                                         '%Y-%m-%d %H:%M:%S'),
                                     original_url=link.url_clean,
                                     canonical_url=link.canonical)

        # If the message is a DM / message, check for opt-out and opt-back-in requests
        elif message.type == "unknown":
            subject = message.subject.lower()
            if subject == "opt me out of amputatorbot":
                try:
                    author = message.author.name
                    log.info(f"New opt-out request by {author}")

                    # If the user is already opted out, notify the user
                    if author.casefold() in list(
                            user.casefold() for user in s.disallowed_users):
                        log.warning("User has already opted out!")
                        s.praw_session.redditor(author).message(
                            subject=
                            "You have already opted out of AmputatorBot",
                            message=
                            "You have already opted out, so AmputatorBot won't reply to your comments "
                            "and submissions anymore. You will still be able to see AmputatorBot's replies to "
                            "other people's content. Block u/AmputatorBot if you don't want that either. "
                            "Cheers!")

                    # If the user hasn't been opted out yet, add user to the list and notify the user
                    else:
                        log.info("User has not opted out yet")
                        update_local_data("disallowed_users", author)
                        s.disallowed_users.append(author)
                        s.praw_session.redditor(author).message(
                            subject=
                            "You have successfully opted out of AmputatorBot",
                            message=
                            "You have successfully opted out of AmputatorBot. AmputatorBot won't reply to your "
                            "comments and submissions anymore (although it can take up to 24 hours to fully "
                            "process your opt-out request). You will still be able to see AmputatorBot's "
                            "replies to other people's content. Block u/AmputatorBot if you don't want that "
                            "either. Cheers!")

                except (RedditAPIException, Forbidden, Exception):
                    log.error(traceback.format_exc())
                    log.warning(
                        f"Something went wrong while processing opt-out request {message.fullname}"
                    )

            elif subject == "opt me back in again of amputatorbot":
                try:
                    author = message.author.name
                    log.info(f"New opt-back-in request by {author}")

                    # If the user is not opted out, notify the user
                    if author.casefold() not in list(
                            user.casefold() for user in s.disallowed_users):
                        log.warning("User is not opted out!")
                        s.praw_session.redditor(author).message(
                            subject="You don't have to opt in of AmputatorBot",
                            message=
                            "This opt-back-in feature is meant only for users who choose to opt-out earlier "
                            "but now regret it. At no point did you opt out of AmputatorBot so there's no "
                            "need to opt back in. Cheers!")

                    # If the user has opted out, remove user from the list and notify the user
                    else:
                        log.info("User is currently opted out")
                        remove_local_data("disallowed_users", author)
                        s.disallowed_users.remove(author)
                        s.praw_session.redditor(author).message(
                            subject=
                            "You have successfully opted back in of AmputatorBot",
                            message=
                            "You have successfully opted back in of AmputatorBot, meaning AmputatorBot can "
                            "reply to your comments and submissions again (although it can take up to 24 hours "
                            "to fully process your opt-back-in request). Thank you! Cheers!"
                        )

                except (RedditAPIException, Forbidden, Exception):
                    log.error(traceback.format_exc())
                    log.warning(
                        f"Something went wrong while processing opt-back-in request {message.fullname}"
                    )

            elif "you've been permanently banned from participating in" in subject:
                subreddit = message.subreddit
                if subreddit:
                    log.info(f"New ban issued by r/{subreddit}")
                    is_banned = check_if_banned(subreddit)
                    if is_banned:
                        update_local_data("disallowed_subreddits", subreddit)
                        s.disallowed_subreddits.append(subreddit)
                        log.info(f"Added {subreddit} to disallowed_subreddits")
                else:
                    log.warning(
                        f"Message wasn't send by a subreddit, but by {message.author.name}"
                    )

        else:
            log.warning(f"Unknown message type: {message.type}")
            continue
        log.info("\n")
Exemplo n.º 2
0
def run_bot(type=Type.MENTION,
            use_gac=True,
            reply_to_item=True,
            save_to_database=True):
    # Get the stream instance (contains session, type and data)
    s = stream.get_stream(type)
    log.info("Set up new stream")

    # Start the stream
    for message in s.praw_session.inbox.stream():
        # Mark the item as read
        message.mark_read()

        # Log the message type and id
        log.info(f"New message: {message.type}: {message.fullname}")

        # If the message is a comment_reply, ignore it
        if message.type == "comment_reply":
            continue

        # If the message is an username_mention, start summon process
        if message.type == "username_mention":
            parent = message.parent()
            i = Item(type=Type.MENTION,
                     id=parent.name,
                     subreddit=parent.subreddit,
                     author=parent.author,
                     context=message.context,
                     summoner=message.author,
                     parent_link=parent.permalink)

            # Check if the parent is a comment or submission
            if isinstance(parent, praw.models.Comment):
                i.body = parent.body
                i.parent_type = Type.COMMENT
            elif isinstance(parent, praw.models.Submission):
                i.body = get_submission_body(parent)
                i.parent_type = Type.SUBMISSION
            else:
                log.warning("Unknown parent instance")

            # Check if the item meets the criteria
            meets_criteria, result_code = check_criteria(
                item=i,
                data=s,
                history_err=s.mentions_err,
                history_ok=s.mentions_ok,
                mustBeAMP=True,
                mustBeNew=True,
                mustNotBeBannedInSubreddit=False,
                mustNotHaveFailed=True,
                mustNotBeMine=True,
                mustNotBeOptedOut=True)

            # If it meets the criteria, try to find the canonicals and make a reply
            if meets_criteria:
                log.info(f"{i.id} in r/{i.subreddit} meets criteria")
                # Get the urls from the body and try to find the canonicals
                urls = get_urls(i.body)
                i.links = get_urls_info(urls, use_gac)

                # If a canonical was found, generate a reply, otherwise log a warning
                if any(link.canonical
                       for link in i.links) or any(link.amp_canonical
                                                   for link in i.links):
                    # Generate a reply
                    reply_text, reply_canonical_text = generate_reply(
                        links=i.links,
                        stream_type=s.type,
                        np_subreddits=s.np_subreddits,
                        item_type=i.parent_type,
                        subreddit=i.subreddit,
                        summoned_link=i.context)

                    # Send a DM if AmputatorBot can't reply because it's banned by a subreddit or user
                    if check_if_banned(i.subreddit, keepLog=False):
                        result_code = ResultCode.ERROR_BANNED

                    if result_code == ResultCode.ERROR_USER_OPTED_OUT or result_code == ResultCode.ERROR_BANNED:

                        # Generate and send an error DM dynamically based on the error
                        subject, message = dm_generator(
                            result_code=result_code,
                            parent_link=i.parent_link,
                            parent_subreddit=i.subreddit,
                            parent_type=i.parent_type.value,
                            links=i.links,
                            canonical_text=reply_canonical_text)

                        s.praw_session.redditor(str(i.summoner)).message(
                            subject, message)
                        log.info(f"Send summoner DM of type {result_code}")

                    # Try to post the reply, send a DM to the summoner
                    elif reply_to_item:
                        try:
                            reply = parent.reply(reply_text)
                            log.info(f"Replied to {i.id} with {reply.name}")
                            update_local_data("mentions_ok", i.id)
                            s.mentions_ok.append(i.id)

                            # Generate and send a SUCCESS DM to the summoner
                            result_code = ResultCode.SUCCESS
                            subject, message = dm_generator(
                                result_code=result_code,
                                reply_link=reply.permalink,
                                parent_subreddit=i.subreddit,
                                parent_type=i.parent_type.value,
                                parent_link=i.parent_link,
                                links=i.links,
                                canonical_text=reply_canonical_text)

                            s.praw_session.redditor(str(i.summoner)).message(
                                subject, message)
                            log.info(f"Send summoner DM of type {result_code}")

                        except (Forbidden, Exception):
                            log.warning("Couldn't post reply!")
                            log.error(traceback.format_exc())
                            update_local_data("mentions_err", i.id)
                            s.mentions_err.append(i.id)

                            # Generate and send an ERROR_REPLY_FAILED DM to the summoner
                            result_code = ResultCode.ERROR_REPLY_FAILED
                            subject, message = dm_generator(
                                result_code=result_code,
                                parent_type=i.parent_type.value,
                                parent_link=i.parent_link,
                                links=i.links,
                                canonical_text=reply_canonical_text)

                            s.praw_session.redditor(str(i.summoner)).message(
                                subject, message)
                            log.info(f"Send summoner DM of type {result_code}")

                # If no canonicals were found, log the failed attempt
                else:
                    log.warning("No canonicals found")
                    update_local_data("mentions_err", i.id)
                    s.mentions_err.append(i.id)

                    # Check if the domain is problematic (meaning it's raising frequent errors)
                    if any(link.origin
                           and link.origin.domain in s.problematic_domains
                           for link in i.links):
                        result_code = ResultCode.ERROR_PROBLEMATIC_DOMAIN
                    else:
                        result_code = ResultCode.ERROR_NO_CANONICALS

                    # Generate and send an error DM to the summoner
                    subject, message = dm_generator(
                        result_code=result_code,
                        parent_type=i.parent_type.value,
                        parent_link=i.parent_link,
                        links=i.links)

                    s.praw_session.redditor(str(i.summoner)).message(
                        subject, message)

                save_entry(save_to_database=save_to_database,
                           entry_type=type.value,
                           links=i.links)

        # If the message is a DM / message, check for opt-out and opt-back-in requests
        elif message.type == "unknown":
            subject = message.subject.lower()
            if subject == "opt me out of amputatorbot":
                try:
                    author = message.author.name
                    log.info(f"New opt-out request by {author}")

                    # If the user is already opted out, notify the user
                    if author.casefold() in list(
                            user.casefold() for user in s.disallowed_users):
                        log.warning("User has already opted out!")
                        s.praw_session.redditor(author).message(
                            subject=
                            "You have already opted out of AmputatorBot",
                            message=
                            "You have already opted out, so AmputatorBot won't reply to your comments "
                            "and submissions anymore. Another option to consider is blocking "
                            "u/AmputatorBot. Cheers!")

                    # If the user hasn't been opted out yet, add user to the list and notify the user
                    else:
                        log.info("User has not opted out yet")
                        update_local_data("disallowed_users",
                                          author,
                                          unique=True)
                        s.disallowed_users.append(author)
                        s.praw_session.redditor(author).message(
                            subject=
                            "You have successfully opted out of AmputatorBot",
                            message=
                            "You have successfully opted out of AmputatorBot. AmputatorBot won't reply to your "
                            "comments and submissions anymore (although it can take up to 24 hours to fully "
                            "process your opt-out request). Another option to consider is blocking "
                            "u/AmputatorBot. Cheers!")

                except (RedditAPIException, Forbidden, Exception):
                    log.error(traceback.format_exc())
                    log.warning(
                        f"Something went wrong while processing opt-out request {message.fullname}"
                    )

            elif subject == "opt me back in again of amputatorbot":
                try:
                    author = message.author.name
                    log.info(f"New opt-back-in request by {author}")

                    # If the user is not opted out, notify the user
                    if author.casefold() not in list(
                            user.casefold() for user in s.disallowed_users):
                        log.warning("User is not opted out!")
                        s.praw_session.redditor(author).message(
                            subject="You don't have to opt in of AmputatorBot",
                            message=
                            "This opt-back-in feature is meant only for users who choose to opt-out earlier "
                            "but now regret it. At no point did you opt out of AmputatorBot, so there's no "
                            "need to opt back in. Cheers!")

                    # If the user has opted out, remove user from the list and notify the user
                    else:
                        log.info("User is currently opted out")
                        remove_local_data("disallowed_users", author)
                        s.disallowed_users.remove(author)
                        s.praw_session.redditor(author).message(
                            subject=
                            "You have successfully opted back in of AmputatorBot",
                            message=
                            "You have successfully opted back in of AmputatorBot, meaning AmputatorBot can "
                            "reply to your comments and submissions again (although it can take up to 24 hours "
                            "to fully process your opt-back-in request). Thank you! Cheers!"
                        )

                except (RedditAPIException, Forbidden, Exception):
                    log.error(traceback.format_exc())
                    log.warning(
                        f"Something went wrong while processing opt-back-in request {message.fullname}"
                    )

        else:
            log.warning(f"Unknown message type: {message.type}")
            continue
        log.info("\n")