Ejemplo n.º 1
0
 def command(cls, irc_c, msg, cmd):
     if len(cmd.args['root']) < 1:
         raise CommandError("Specify a page's URL whose shortest search "
                            "term you want to find.")
     pages = [DB.get_article_info(p_id)['title']
              for p_id in DB.get_articles([])]
     try:
         title = DB.get_article_info(
             DB.get_articles(
                 [{'type': 'url', 'term': cmd.args['root'][0]}]
             )[0])['title']
     except IndexError:
         raise MyFaultError("I couldn't find the page with that URL.")
     single_string = shortest.get_substring(title, pages)
     print("Single string:", single_string)
     helen_style = shortest.get_multi_substring(title, pages)
     if single_string is None and helen_style is None:
         raise MyFaultError("There's no unique search for {} (\"{}\")"
                            .format(cmd.args['root'][0], title))
     msg.reply("Shortest search for \x02{}\x0F · {}"
               .format(cmd.args['root'][0],
                       shortest.pick_answer(single_string, helen_style)))
Ejemplo n.º 2
0
    def command(cls, irc_c, msg, cmd):
        # Check that we are actually able to do this
        # (might have to move to end for defer check)
        if (defer.check(cmd, 'jarvis', 'Secretary_Helen')):
            return
        # Parse the command itself
        search.expandargs(cmd)
        # check to see if there are any arguments
        if len(cmd.args) == 1 and len(cmd.args['root']) == 0:
            raise CommandError("Must specify at least one search term")
        # fullname is deprecated for tars
        if 'fullname' in cmd:
            raise CommandError("TARS does not support fullname search - "
                               "wrap your search in quotemarks instead")
        # Set the return mode of the output
        selection = {
            'ignorepromoted': 'ignorepromoted' in cmd,
            'order': 'fuzzy',
            'limit': None,
            'offset': 0
        }
        # order, limit, offset
        if 'order' in cmd:
            if len(cmd['order']) != 1:
                raise CommandError("When using the order argument "
                                   "(--order/-o), exactly one order type must "
                                   "be specified")
            if cmd['order'][0] in ['recent', 'recommend', 'random', 'fuzzy', 'none']:
                if cmd['order'] == 'none':
                    selection['order'] = None
                else:
                    selection['order'] = cmd['order'][0]
            else:
                raise CommandError("Selection return order ('{}') must be "
                                   "one of: recent, recommend, random, "
                                   "fuzzy, none".format(cmd['order'][0]))
        if 'limit' in cmd:
            if len(cmd['limit']) != 1:
                raise CommandError("When using the limit argument "
                                   "(--limit/-l), exactly one limit must "
                                   "be specified")
            if isint(cmd['limit'][0]):
                if int(cmd['limit'][0]) > 0:
                    selection['limit'] = int(cmd['limit'][0])
                elif int(cmd['limit'][0]) == 0:
                    selection['limit'] = None
                else:
                    raise CommandError("When using the limit argument "
                                       "(--limit/-l), the limit must be at "
                                       "least 0")
            else:
                raise CommandError("When using the limit argument "
                                   "(--limit/-l), the limit must be an integer")
        if 'offset' in cmd:
            if len(cmd['offset']) != 1:
                raise CommandError("When using the offset argument "
                                   "(--offset/-f), exactly one offset must "
                                   "be specified")
            if isint(cmd['offset'][0]):
                if int(cmd['offset'][0]) >= 0:
                    selection['offset'] = int(cmd['offset'][0])
                else:
                    raise CommandError("When using the offset argument "
                                       "(--offset/-f), the offset must be at "
                                       "least 0")
            else:
                raise CommandError("When using the offset argument "
                                   "(--offset/-f), the offset must be an integer")
        if 'random' in cmd:
            selection['order'] = 'random'
            selection['limit'] = 1
        if 'recommend' in cmd:
            selection['order'] = 'recommend'
            selection['limit'] = 1
        if 'newest' in cmd:
            selection['order'] = 'recent'
            selection['limit'] = 1
        # What are we searching for?
        searches = []
        strings = []
        if len(cmd.args['root']) > 0:
            strings = cmd.args['root']
            searches.extend([{'term': s, 'type': None} for s in strings])
        # Add any regexes
        regexes = []
        if 'regex' in cmd:
            if len(cmd['regex']) == 0:
                raise CommandError(
                    "When using the regular expression filter "
                    "(--regex/-x), at least one regex must "
                    "be specified"
                )
            for regex in cmd['regex']:
                try:
                    re.compile(regex)
                except re.error as e:
                    raise CommandError(
                        "'{}' isn't a valid regular expression: {}"
                        .format(regex, e)
                    )
                regexes.append(regex)
                # don't append the compiled - SQL doesn't like that
            searches.extend([{'term': r, 'type': 'regex'} for r in regexes])
        # Set the tags
        tags = {'include': [], 'exclude': []}
        if 'tags' in cmd:
            if len(cmd['tags']) == 0:
                raise CommandError(
                    "When using the tag filter (--tag/-t), at "
                    "least one tag must be specified"
                )
            for tag in cmd['tags']:
                if tag[0] == "-":
                    tags['exclude'].append(tag[1:])
                    continue
                if tag[0] == "+":
                    tags['include'].append(tag[1:])
                    continue
                tags['include'].append(tag)
            searches.append({'term': tags, 'type': 'tags'})
        # Set the author
        authors = {'include': [], 'exclude': []}
        if 'author' in cmd:
            if len(cmd['author']) == 0:
                raise CommandError(
                    "When using the author filter "
                    "(--author/-a), at least one author must "
                    "be specified"
                )
            for author in cmd['author']:
                if author[0] == "-":
                    authors['exclude'].append(author[1:])
                    continue
                if author[0] == "+":
                    authors['include'].append(author[1:])
                    continue
                authors['include'].append(author)
            searches.append({'term': authors, 'type': 'author'})
        # Set the rating
        # Cases to account for: modifiers, range, combination
        ratings = MinMax()
        if 'rating' in cmd:
            if len(cmd['rating']) == 0:
                raise CommandError(
                    "When using the rating filter "
                    "(--rating/-r), at least one rating must "
                    "be specified"
                )
            for rating in cmd['rating']:
                if ".." in rating:
                    rating = rating.split("..")
                    if len(rating) > 2:
                        raise CommandError("Too many ratings in range")
                    try:
                        rating = [int(x) for x in rating]
                    except ValueError:
                        raise CommandError(
                            "Ratings in a range must be plain numbers"
                        )
                    try:
                        ratings >= min(rating)
                        ratings <= max(rating)
                    except MinMaxError as e:
                        raise CommandError(str(e).format("rating"))
                elif rating[0] in [">", "<", "="]:
                    pattern = r"^(?P<comp>[<>=]{1,2})(?P<value>[0-9]+)"
                    match = re.search(pattern, rating)
                    if match:
                        try:
                            rating = int(match.group('value'))
                        except ValueError:
                            raise CommandError("Invalid rating comparison")
                        comp = match.group('comp')
                        try:
                            if comp == ">=":
                                ratings >= rating
                            elif comp == "<=":
                                ratings <= rating
                            elif comp == "<":
                                ratings < rating
                            elif comp == ">":
                                ratings > rating
                            elif comp == "=":
                                ratings >= rating
                                ratings <= rating
                            else:
                                raise CommandError(
                                    "Unknown operator in rating comparison"
                                )
                        except MinMaxError as e:
                            raise CommandError(str(e).format("rating"))
                    else:
                        raise CommandError("Invalid rating comparison")
                else:
                    try:
                        rating = int(rating)
                    except ValueError:
                        raise CommandError(
                            "Rating must be a range, comparison, or number"
                        )
                    # Assume =, assign both
                    try:
                        ratings >= rating
                        ratings <= rating
                    except MinMaxError as e:
                        raise CommandError(str(e).format("rating"))
            searches.append({'term': ratings, 'type': 'rating'})
        # Set created date
        # Cases to handle: absolute, relative, range (which can be both)
        createds = MinMax()
        if 'created' in cmd:
            if len(cmd['created']) == 0:
                raise CommandError(
                    "When using the date of creation filter "
                    "(--created/-c), at least one date must "
                    "be specified"
                )
            created = cmd['created']
            # created is a list of date selectors - ranges, abs and rels
            # but ALL dates are ranges!
            created = [DateRange(c) for c in created]
            # created is now a list of DateRanges with min and max
            try:
                for key, selector in enumerate(created):
                    if selector.max is not None:
                        createds <= selector.max
                    if selector.min is not None:
                        createds >= selector.min
            except MinMaxError as e:
                raise CommandError(str(e).format("date"))
            searches.append({'term': createds, 'type': 'date'})
        # Set category
        categories = {'include': [], 'exclude': []}
        if 'category' in cmd:
            if len(cmd['category']) == 0:
                raise CommandError(
                    "When using the category filter "
                    "(--category/-y), at least one category "
                    "must be specified"
                )
            for category in cmd['category']:
                if category[0] == "-":
                    categories['exclude'].append(category[1:])
                    continue
                if category[0] == "+":
                    categories['include'].append(category[1:])
                    continue
                categories['include'].append(category)
            searches.append({'term': categories, 'type': 'category'})
        # Set parent page
        parents = None
        if 'parent' in cmd:
            if len(cmd['parent']) != 1:
                raise CommandError(
                    "When using the parent page filter "
                    "(--parent/-p), exactly one parent URL "
                    "must be specified"
                )
            parents = cmd['parent'][0]
            searches.append({'term': parents, 'type': 'parent'})
        # FINAL BIT - summarise commands
        if 'verbose' in cmd:
            verbose = "Searching for articles "
            if len(strings) > 0:
                verbose += (
                    "containing \"{}\"; ".format("\", \"".join(strings))
                )
            if len(regexes) > 0:
                verbose += "matching the regex /{}/; ".format(
                    "/ & /".join(regexes)
                )
            if parents is not None:
                verbose += ("whose parent page is '{}'; ".format(parents))
            if len(categories['include']) == 1:
                verbose += (
                    "in the category '" + categories['include'][0] + "'; "
                )
            elif len(categories['include']) > 1:
                verbose += (
                    "in the categories '" + "', '".join(categories) + "; "
                )
            if len(categories['exclude']) == 1:
                verbose += (
                    "not in the category '" + categories['exclude'][0] + "'; "
                )
            elif len(categories['exclude']) > 1:
                verbose += (
                    "not in the categories '" + "', '".join(categories) + "; "
                )
            if len(tags['include']) > 0:
                verbose += (
                    "with the tags '" + "', '".join(tags['include']) + "'; "
                )
            if len(tags['exclude']) > 0:
                verbose += (
                    "without the tags '" + "', '".join(tags['exclude']) + "'; "
                )
            if len(authors['include']) > 0:
                verbose += ("by " + " & ".join(authors['include']) + "; ")
            if len(authors['exclude']) > 0:
                verbose += ("not by " + " or ".join(authors['exclude']) + "; ")
            if ratings['max'] is not None and ratings['min'] is not None:
                if ratings['max'] == ratings['min']:
                    verbose += (
                        "with a rating of " + str(ratings['max']) + "; "
                    )
                else:
                    verbose += (
                        "with a rating between " + str(ratings['min']) +
                        " and " + str(ratings['max']) + "; "
                    )
            elif ratings['max'] is not None:
                verbose += (
                    "with a rating less than " + str(ratings['max'] + 1) + "; "
                )
            elif ratings['min'] is not None:
                verbose += (
                    "with a rating greater than " + str(ratings['min'] - 1) +
                    "; "
                )
            if createds['min'] is not None and createds['max'] is not None:
                verbose += (
                    "created between " + createds['min'].to_datetime_string() +
                    " and " + createds['max'].to_datetime_string() + "; "
                )
            elif createds['max'] is not None:
                verbose += (
                    "created before " + createds['max'].to_datetime_string() +
                    "; "
                )
            elif createds['min'] is not None:
                verbose += (
                    "created after " + createds['min'].to_datetime_string() +
                    "; "
                )
            if verbose.endswith("; "):
                verbose = verbose[:-2]
            msg.reply(verbose)
            pprint(searches)

        page_ids = DB.get_articles(searches)
        pages = [DB.get_article_info(p_id) for p_id in page_ids]
        pages = search.order(pages, search_term=strings, **selection)

        if len(pages) >= 50:
            msg.reply("{} results found - you're going to have to be more "
                      "specific!".format(len(pages)))
            return
        if len(pages) > 3:
            msg.reply("{} results (use ..sm to choose): {}".format(
                len(pages), showmore.parse_multiple_titles(pages)))
            DB.set_showmore_list(msg.raw_channel, [p['id'] for p in pages])
            return
        if len(pages) == 0:
            # check if there's no args other than --verbose
            if set(cmd.args).issubset({'root', 'verbose'}):
                # google only takes 10 args
                url = google_search(
                    '"' + '" "'.join(cmd.args['root'][:10]) + '"', num=1
                )[0]
                if url is None:
                    msg.reply("No matches found.")
                    return
                #pprint.pprint(url)
                if url['title'].endswith(" - SCP Foundation"):
                    url['title'] = url['title'][:-17]
                msg.reply(
                    "No matches found. Did you mean \x02{}\x0F? {}"
                    .format(url['title'], url['link'])
                )
            else:
                msg.reply("No matches found.")
            return
        for page in pages:
            msg.reply(gib.obfuscate(showmore.parse_title(page),
                                    DB.get_channel_members(msg.raw_channel)))