예제 #1
0
def populate_subreddit_lists(cfg: Config) -> None:
    """
    Gets the list of subreddits to monitor and loads it into memory.

    :return: None.
    """

    cfg.subreddits_to_check = get_wiki_page("subreddits", cfg).splitlines()
    cfg.subreddits_to_check = clean_list(cfg.subreddits_to_check)
    log.debug(
        f"Created list of subreddits from wiki: {cfg.subreddits_to_check}")

    for line in get_wiki_page("subreddits/upvote-filtered", cfg).splitlines():
        if "," in line:
            sub, threshold = line.split(",")
            cfg.upvote_filter_subs[sub] = int(threshold)

    log.debug(
        f"Retrieved subreddits subject to the upvote filter: {cfg.upvote_filter_subs}"
    )

    cfg.subreddits_domain_filter_bypass = clean_list(
        get_wiki_page("subreddits/domain-filter-bypass", cfg).splitlines())
    log.debug(
        f"Retrieved subreddits that bypass the domain filter: {cfg.subreddits_domain_filter_bypass}"
    )

    cfg.no_link_header_subs = clean_list(
        get_wiki_page("subreddits/no-link-header", cfg).splitlines())
    log.debug(
        f"Retrieved subreddits subject to the upvote filter: {cfg.no_link_header_subs}"
    )
예제 #2
0
def populate_formatting(cfg: Config) -> None:
    """
    Grabs the contents of the three wiki pages that contain the
    formatting examples and stores them in the cfg object.

    :return: None.
    """
    cfg.audio_formatting = get_wiki_page("formats/audio", cfg)
    cfg.video_formatting = get_wiki_page("formats/video", cfg)
    cfg.image_formatting = get_wiki_page("format/images", cfg)
예제 #3
0
def populate_domain_lists(cfg):
    """
    Loads the approved content domains into the config object from the
    wiki page.

    :return: None.
    """

    cfg.video_domains = []
    cfg.image_domains = []
    cfg.audio_domains = []

    domains = get_wiki_page('domains', cfg)
    domains = ''.join(domains.splitlines()).split('---')

    for domainset in domains:
        domain_list = domainset[domainset.index('['):].strip('[]').split(', ')
        current_domain_list = []
        if domainset.startswith('video'):
            current_domain_list = cfg.video_domains
        elif domainset.startswith('audio'):
            current_domain_list = cfg.audio_domains
        elif domainset.startswith('images'):
            current_domain_list = cfg.image_domains

        current_domain_list += domain_list
        # [current_domain_list.append(x) for x in domain_list]
        logging.debug(f'Domain list populated: {current_domain_list}')
예제 #4
0
def populate_formatting(cfg):
    """
    Grabs the contents of the three wiki pages that contain the
    formatting examples and stores them in the cfg object.

    :return: None.
    """
    # zero out everything so we can reinitialize later
    cfg.audio_formatting = ''
    cfg.video_formatting = ''
    cfg.image_formatting = ''
    cfg.other_formatting = ''

    cfg.audio_formatting = get_wiki_page('format/audio', cfg)
    cfg.video_formatting = get_wiki_page('format/video', cfg)
    cfg.image_formatting = get_wiki_page('format/images', cfg)
    cfg.other_formatting = get_wiki_page('format/other', cfg)
예제 #5
0
def populate_subreddit_lists(cfg):
    """
    Gets the list of subreddits to monitor and loads it into memory.

    :return: None.
    """

    cfg.subreddits_to_check = []
    cfg.upvote_filter_subs = {}
    cfg.no_link_header_subs = []

    cfg.subreddits_to_check = get_wiki_page('subreddits', cfg).splitlines()
    cfg.subreddits_to_check = clean_list(cfg.subreddits_to_check)
    logging.debug(
        f'Created list of subreddits from wiki: {cfg.subreddits_to_check}')

    for line in get_wiki_page('subreddits/upvote-filtered', cfg).splitlines():
        if ',' in line:
            sub, threshold = line.split(',')
            cfg.upvote_filter_subs[sub] = int(threshold)

    logging.debug(f'Retrieved subreddits subject to the upvote filter: '
                  f'{cfg.upvote_filter_subs} ')

    cfg.subreddits_domain_filter_bypass = get_wiki_page(
        'subreddits/domain-filter-bypass', cfg).split('\r\n')
    cfg.subreddits_domain_filter_bypass = clean_list(
        cfg.subreddits_domain_filter_bypass)
    logging.debug(f'Retrieved subreddits that bypass the domain filter: '
                  f'{cfg.subreddits_domain_filter_bypass} ')

    cfg.no_link_header_subs = get_wiki_page('subreddits/no-link-header',
                                            cfg).split('\r\n')
    cfg.no_link_header_subs = clean_list(cfg.no_link_header_subs)
    logging.debug(f'Retrieved subreddits subject to the upvote filter: '
                  f'{cfg.no_link_header_subs} ')

    lines = get_wiki_page('subreddits/archive-time', cfg).splitlines()
    cfg.archive_time_default = int(lines[0])
    cfg.archive_time_subreddits = {}
    for line in lines[1:]:
        if ',' in line:
            sub, time = line.split(',')
            cfg.archive_time_subreddits[sub.lower()] = int(time)
예제 #6
0
def process_coc(username: str, context: str, blossom_submission: Dict,
                cfg: Config) -> Tuple:
    """
    Process the acceptation of the CoC by the specified user.

    :param username: The name of the user accepting the CoC
    :param context: The context of the reply, to use as a link
    :param blossom_submission: The corresponding Submission in Blossom
    :param cfg: Config of tor
    """
    user_response = cfg.blossom.get_user(username=username)
    if user_response.status == BlossomStatus.ok:
        # The status codes of accepting the CoC are not checked because they are already
        # caught by getting the user.
        response = cfg.blossom.accept_coc(username=username)
        new_acceptance = response.status == BlossomStatus.ok
        if new_acceptance:
            emote = random.choice(MODCHAT_EMOTES)
            user_url = i18n["urls"]["reddit_url"].format(f"/u/{username}")
            post_url = i18n["urls"]["reddit_url"].format(context)
            send_to_modchat(
                f"<{user_url}|u/{username}> has just "
                f"<{post_url}|accepted the CoC!> {emote}",
                cfg,
                channel="CAZN8J078",
            )
        return process_claim(username,
                             blossom_submission,
                             cfg,
                             first_time=new_acceptance)
    elif user_response.status == BlossomStatus.not_found:
        cfg.blossom.create_user(username=username)
        return (
            i18n["responses"]["general"]["coc_not_accepted"].format(
                get_wiki_page("codeofconduct", cfg)),
            None,
        )
    else:
        return process_claim(username, blossom_submission, cfg)
예제 #7
0
def process_unclaim(username: str, blossom_submission: Dict,
                    submission: Submission, cfg: Config) -> Tuple:
    """
    Process an unclaim request.

    Note that this function also checks whether a post should be removed and
    does so when required.

    :param username: The name of the user unclaiming the submission
    :param blossom_submission: The relevant Submission of Blossom
    :param submission: The relevant Submission in Reddit
    :param cfg: Config of tor
    """
    response = cfg.blossom.unclaim(submission_id=blossom_submission["id"],
                                   username=username)
    return_flair = None
    unclaim_messages = i18n["responses"]["unclaim"]
    if response.status == BlossomStatus.ok:
        message = unclaim_messages["success"]
        return_flair = flair.unclaimed
        removed, reported = remove_if_required(submission,
                                               blossom_submission["id"], cfg)
        if removed:
            # Select the message based on whether the post was reported or not.
            message = unclaim_messages["success_with_report" if reported else
                                       "success_without_report"]
    elif response.status == BlossomStatus.not_found:
        message = i18n["responses"]["general"]["coc_not_accepted"].format(
            get_wiki_page("codeofconduct", cfg))
        cfg.blossom.create_user(username)
    elif response.status == BlossomStatus.other_user:
        message = unclaim_messages["claimed_other_user"]
    elif response.status == BlossomStatus.already_completed:
        message = unclaim_messages["post_already_completed"]
    elif response.status == BlossomStatus.blacklisted:
        message = i18n["responses"]["general"]["blacklisted"]
    else:
        message = unclaim_messages["still_unclaimed"]
    return message, return_flair
예제 #8
0
def populate_domain_lists(cfg: Config) -> None:
    """
    Loads the approved content domains into the config object from the
    wiki page.

    :return: None.
    """

    domain_string = get_wiki_page("domains", cfg)
    domains = "".join(domain_string.splitlines()).split("---")

    for domainset in domains:
        domain_list = domainset[domainset.index("["):].strip("[]").split(", ")
        current_domain_list = []
        if domainset.startswith("video"):
            current_domain_list = cfg.video_domains
        elif domainset.startswith("audio"):
            current_domain_list = cfg.audio_domains
        elif domainset.startswith("images"):
            current_domain_list = cfg.image_domains

        current_domain_list += domain_list
        # [current_domain_list.append(x) for x in domain_list]
        log.debug(f"Domain list populated: {current_domain_list}")
예제 #9
0
def populate_header(cfg: Config) -> None:
    cfg.header = get_wiki_page("format/header", cfg)
예제 #10
0
def populate_gifs(cfg: Config) -> None:
    cfg.no_gifs = get_wiki_page("usefulgifs/no", cfg).splitlines()
예제 #11
0
def process_claim(username: str,
                  blossom_submission: Dict,
                  cfg: Config,
                  first_time=False) -> Tuple:
    """
    Process a claim request.

    This function sends a reply depending on the response from Blossom and
    creates an user when this is the first time a user uses the bot.

    :param username: Name of the user claiming the submission
    :param blossom_submission: The relevant submission in Blossom
    :param cfg: Config of tor
    :param first_time: Whether this is the first time a user claims something
    """
    coc_not_accepted = i18n["responses"]["general"]["coc_not_accepted"]

    response = cfg.blossom.claim(submission_id=blossom_submission["id"],
                                 username=username)
    return_flair = None
    if response.status == BlossomStatus.ok:
        # A random tip to append to the response
        random_tip = i18n["tips"]["message"].format(
            tip_message=random.choice(i18n["tips"]["collection"]))

        message = (i18n["responses"]["claim"]
                   ["first_claim_success" if first_time else "success"] +
                   "\n\n" + random_tip)

        return_flair = flair.in_progress
        log.info(
            f'Claim on Submission {blossom_submission["tor_url"]} by {username} successful.'
        )

    elif response.status == BlossomStatus.coc_not_accepted:
        message = coc_not_accepted.format(get_wiki_page("codeofconduct", cfg))

    elif response.status == BlossomStatus.not_found:
        message = coc_not_accepted.format(get_wiki_page("codeofconduct", cfg))
        cfg.blossom.create_user(username=username)

    elif response.status == BlossomStatus.blacklisted:
        message = i18n["responses"]["general"]["blacklisted"]

    elif response.status == BlossomStatus.already_claimed:
        claimed_by = response.data["username"]
        if claimed_by == username:
            # This user already got the submission
            message = i18n["responses"]["claim"]["already_claimed_by_self"]
        else:
            # The submission was claimed by someone else
            message = i18n["responses"]["claim"][
                "already_claimed_by_someone"].format(claimed_by=claimed_by)

    elif response.status == BlossomStatus.too_many_claims:
        claimed_links = [submission["tor_url"] for submission in response.data]
        message = i18n["responses"]["claim"]["too_many_claims"].format(
            links="\n".join(f"- {link}" for link in claimed_links), )

    else:
        message = i18n["responses"]["general"]["oops"]

    return message, return_flair
예제 #12
0
def process_done(
    user: Redditor,
    blossom_submission: Dict,
    comment: Comment,
    cfg: Config,
    override=False,
    alt_text_trigger=False,
) -> Tuple:
    """
    Handles comments where the user claims to have completed a post.

    This function sends a reply to the user depending on the responses received
    from Blossom.

    :param user: The user claiming his transcription is done
    :param blossom_submission: The relevant submission in Blossom
    :param comment: The comment of the user, used to retrieve the user's flair
    :param cfg: the global config object.
    :param override: whether the validation check should be skipped
    :param alt_text_trigger: whether there is an alternative to "done" that has
                             triggered this function.
    """
    return_flair = None
    done_messages = i18n["responses"]["done"]
    # This is explicitly missing the format call that adds the code of
    # conduct text because if we populate it here, we will fetch the wiki
    # page on _every single `done`_ and that's just silly. Only populate
    # it if it's necessary.
    coc_not_accepted = i18n["responses"]["general"]["coc_not_accepted"]

    blossom_user = cfg.blossom.get_user(username=user.name)
    if blossom_user.status != BlossomStatus.ok:
        # If we don't know who the volunteer is, then we don't have a record of
        # them and they need to go through the code of conduct process.
        return (
            coc_not_accepted.format(get_wiki_page("codeofconduct", cfg)),
            return_flair,
        )

    if not blossom_user.data["accepted_coc"]:
        # If the volunteer in question hasn't accepted the code of conduct,
        # eject early and return. Although the `create_transcription` endpoint
        # returns a code of conduct check, we only hit it when we create a
        # transcription, which requires that they wrote something. If a volunteer
        # just writes `done` without putting a transcription down, it will hit
        # this edge case.
        return (
            coc_not_accepted.format(get_wiki_page("codeofconduct", cfg)),
            return_flair,
        )

    transcription, is_visible = get_transcription(blossom_submission["url"],
                                                  user, cfg)

    message = done_messages["cannot_find_transcript"]  # default message

    if not transcription:
        # When the user replies `done` quickly after posting the transcription,
        # it might not be available on Reddit yet. Wait a bit and try again.
        time.sleep(1)
        transcription, is_visible = get_transcription(
            blossom_submission["url"], user, cfg)

    if transcription and not override:
        # Try to detect common formatting errors
        formatting_errors = check_for_formatting_issues(transcription.body)
        if len(formatting_errors) > 0:
            # Formatting issues found.  Reject the `done` and ask the
            # volunteer to fix them.
            issues = ", ".join([error.value for error in formatting_errors])
            # TODO: Re-evaluate if this is necessary
            # This is more of a temporary thing to see how the
            # volunteers react to the bot.
            send_to_modchat(
                i18n["mod"]["formatting_issues"].format(
                    author=user.name,
                    issues=issues,
                    link=f"https://reddit.com{comment.context}",
                ),
                cfg,
                "formatting-issues",
            )
            message = get_formatting_issue_message(formatting_errors)
            return message, return_flair

    if transcription:
        cfg.blossom.create_transcription(
            transcription.id,
            transcription.body,
            i18n["urls"]["reddit_url"].format(str(transcription.permalink)),
            transcription.author.name,
            blossom_submission["id"],
            not is_visible,
        )

    if transcription or override:
        # because we can enter this state with or without a transcription, it
        # makes sense to have this as a separate block.
        done_response = cfg.blossom.done(blossom_submission["id"], user.name,
                                         override)
        # Note that both the not_found and coc_not_accepted status are already
        # caught in the previous lines of code, hence these are not checked again.
        if done_response.status == BlossomStatus.ok:
            return_flair = flair.completed
            set_user_flair(user, comment, cfg)
            log.info(
                f'Done on Submission {blossom_submission["tor_url"]} by {user.name}'
                f" successful.")
            message = done_messages["completed_transcript"]
            transcription_count = blossom_user.data["gamma"] + 1

            if check_promotion(transcription_count):
                additional_message = generate_promotion_message(
                    transcription_count)
                message = f"{message}\n\n{additional_message}"

            if alt_text_trigger:
                message = f"I think you meant `done`, so here we go!\n\n{message}"

        elif done_response.status == BlossomStatus.already_completed:
            message = done_messages["already_completed"]

        elif done_response.status == BlossomStatus.missing_prerequisite:
            message = done_messages["not_claimed_by_user"]

        elif done_response.status == BlossomStatus.blacklisted:
            message = i18n["responses"]["general"]["blacklisted"]

    return message, return_flair
예제 #13
0
def process_claim(post, cfg, first_time=False):
    """
    Handles comment replies containing the word 'claim' and routes
    based on a basic decision tree.

    :param post: The Comment object containing the claim.
    :param cfg: the global config dict.
    :return: None.
    """
    top_parent = get_parent_post_id(post, cfg.r)

    already_claimed = i18n['responses']['claim']['already_claimed']
    claim_already_complete = i18n['responses']['claim']['already_complete']
    please_accept_coc = i18n['responses']['general']['coc_not_accepted']

    if first_time:
        claim_success = i18n['responses']['claim']['first_claim_success']
    else:
        claim_success = i18n['responses']['claim']['success']

    # WAIT! Do we actually own this post?
    if top_parent.author.name not in __BOT_NAMES__:
        logging.debug('Received `claim` on post we do not own. Ignoring.')
        return

    try:
        if not coc_accepted(post, cfg):
            # do not cache this page. We want to get it every time.
            post.reply(
                _(please_accept_coc.format(get_wiki_page('codeofconduct',
                                                         cfg))))
            return

        # this can be either '' or None depending on how the API is feeling
        # today
        if top_parent.link_flair_text in ['', None]:
            # There exists the very small possibility that the post was
            # malformed and doesn't actually have flair on it. In that case,
            # let's set something so the next part doesn't crash.
            flair_post(top_parent, flair.unclaimed)

        if flair.unclaimed in top_parent.link_flair_text:
            # need to get that "Summoned - Unclaimed" in there too
            post.reply(_(claim_success))

            flair_post(top_parent, flair.in_progress)
            logging.info(
                f'Claim on ID {top_parent.fullname} by {post.author} successful'
            )

        # can't claim something that's already claimed
        elif top_parent.link_flair_text == flair.in_progress:
            post.reply(_(already_claimed))
        elif top_parent.link_flair_text == flair.completed:
            post.reply(_(claim_already_complete))

    except praw.exceptions.APIException as e:
        if e.error_type == 'DELETED_COMMENT':
            logging.info(
                f'Comment attempting to claim ID {top_parent.fullname} has '
                f'been deleted. Back up for grabs! ')
            return
        raise  # Re-raise exception if not
예제 #14
0
def populate_header(cfg):
    cfg.header = ''
    cfg.header = get_wiki_page('format/header', cfg)
예제 #15
0
def populate_gifs(cfg):
    # zero it out so we can load more
    cfg.no_gifs = []
    cfg.no_gifs = get_wiki_page('usefulgifs/no', cfg).split('\r\n')