Exemple #1
0
def handle_meme(command: Command):
    """
    `!meme <names | (<MEME NAME> "<TOP TEXT>" "<BOTTOM TEXT>")>`
    Generates a meme of the given format with the provided top and
    bottom text. For a full list of formats, try `!meme names`.
    """
    channel = command.channel_id

    if not command.has_arg():
        raise UsageSyntaxException()

    name = command.arg.split()[0].lower()
    if name == "names":
        send_meme_names(command)
        return
    elif name not in MEME_NAMES.keys():
        bot.post_message(channel, "The meme name is invalid. "
                         "Try `!meme names` to get a list of all valid names")
        return

    args = get_meme_arguments(command.arg)
    if len(args) != 2:
        raise UsageSyntaxException()

    # Make an attachment linking to image
    top, bottom = args
    image_url = API_URL + f"{quote(name)}/{quote(top)}/{quote(bottom)}.jpg"
    attachments = [{"text": "", "image_url": image_url}]
    bot.post_message(channel, "", attachments=attachments)
Exemple #2
0
def handle_bgg(command: Command):
    """
    `!bgg board_game` - Gets the details of `board_game` from Board Game Geek
    """

    if not command.has_arg():
        raise UsageSyntaxException()
    argument = command.arg

    identity = get_bgg_id(argument)
    if identity is None:
        bot.post_message(command.channel_id,
                         "Could not find board game with that name.")
        return

    parameters = get_board_game_parameters(identity)
    if parameters is None:
        bot.post_message(command.channel_id, "Something has gone wrong.")
        return

    message = format_board_game_parameters(parameters)
    bot.post_message(command.channel_id,
                     message,
                     unfurl_links=False,
                     unfurl_media=False)
Exemple #3
0
def define(command: Command):
    '''
    `!define <TEXT>` - Gets the dictionary definition of TEXT
    '''
    query = command.arg
    # Fun Fact: Empty searches return the definition of adagio (a piece of music to be played or
    # sung slowly)
    if not command.has_arg():
        raise UsageSyntaxException()

    http_response = requests.get(API_URL, params={'headword': query})

    # Check if the response is OK
    if http_response.status_code != requests.codes.ok:
        bot.post_message(command.channel_id, "Problem fetching definition")
        return

    json_data = json.loads(http_response.content)
    results = json_data.get('results', [])
    if len(results) == 0:
        message = "No Results"
    else:
        # This gets the first definition of the first result.
        senses = results[0].get('senses', [{}])[0]
        # Sometimes there are "subsenses" for whatever reason and sometimes there aren't.
        # No explanation provided. This gets the first subsense if there are, otherwise,
        # just uses senses.
        message = senses.get('subsenses',
                             [senses])[0].get('definition',
                                              "Definition not available")

    bot.post_message(command.channel_id, f">>>{message}")
Exemple #4
0
def handle_latex_command(command: Command):
    """
    `!latex CONTENT` - Renders `CONTENT` to LaTeX and sends it to Slack.
    `$$ CONTENT $$` also works.
    """
    if not command.has_arg():
        raise UsageSyntaxException()
    handle_latex_internal(command.channel_id, command.arg.strip())
Exemple #5
0
def handle_http(command: Command):
    '''
    `!http <CODE>` - Returns a HTTP cat.
    '''
    if not command.has_arg():
        raise UsageSyntaxException()

    try:
        http_code = int(command.arg.strip())
    except ValueError as e:
        raise UsageSyntaxException()

    if http_code not in AVAILABLE_CODES:
        bot.post_message(command.channel_id, f'HTTP cat {http_code} is not available')
        return

    bot.post_message(command.channel_id, f'https://http.cat/{http_code}')
Exemple #6
0
def handle_urban(command: Command) -> None:
    """
    `!urban <PHRASE>` - Looks a phrase up on Urban Dictionary.
    """
    # Check for search phase
    if not command.has_arg():
        raise UsageSyntaxException()

    search_term = command.arg

    # Attempt to get definitions from the Urban Dictionary API.
    http_response = requests.get(URBAN_API_ENDPOINT,
                                 params={'term': search_term})
    if http_response.status_code != 200:
        bot.post_message(
            command.channel_id,
            'There was an error accessing the Urban Dictionary API.')
        return
    results = http_response.json()

    # Filter for exact matches
    filtered_definitions = filter(
        lambda def_: def_['word'].casefold() == search_term.casefold(),
        results['list'])

    # Sort definitions by their number of thumbs ups.
    sorted_definitions = sorted(filtered_definitions,
                                key=lambda def_: def_['thumbs_up'],
                                reverse=True)

    # If search phrase is not found, notify user.
    if len(sorted_definitions) == 0:
        bot.post_message(command.channel_id,
                         f'> No results found for {search_term}. ¯\\_(ツ)_/¯')
        return

    best_definition = sorted_definitions[0]
    best_definition_text = re.sub(
        r'[\[\]]', '',
        best_definition["definition"])  # Remove Urban Dictionary [links]

    example_text = re.sub(r'[\[\]]', '', best_definition.get(
        'example', ''))  # Remove Urban Dictionary [links]
    # Break example into individual lines and wrap each in it's own block quote.
    example_lines = example_text.split('\r\n')
    formatted_example = '\n'.join(f'> {line}' for line in example_lines)

    # Format message and send response to user in channel query was sent from.
    message = f'*{search_term.title()}*\n' \
              f'{best_definition_text.capitalize()}\n' \
              f'{formatted_example}'
    # Only link back to Urban Dictionary if there are more definitions.
    if len(sorted_definitions) > 1:
        endpoint_url = http_response.url.replace(URBAN_API_ENDPOINT,
                                                 URBAN_USER_ENDPOINT)
        message += f'\n_ more definitions at {endpoint_url} _'

    bot.post_message(command.channel_id, message)
Exemple #7
0
def parse_arguments(argv: List[str]) -> Namespace:
    """
    Parses !advent arguments from the given list.

    Returns namespace with argument values or throws UsageSyntaxException.
    If an exception is thrown, its message should be shown to the user and
    execution should NOT continue.
    """
    parser = ArgumentParser("!advent", add_help=False)

    parser.add_argument("day",
                        type=int,
                        default=0,
                        nargs="?",
                        help="Show leaderboard for specific day" +
                        " (default: all days)")
    parser.add_argument("-g",
                        "--global",
                        action="store_true",
                        dest="global_",
                        help="Show global points")
    parser.add_argument("-y",
                        "--year",
                        type=int,
                        default=datetime.now().year,
                        help="Year of leaderboard (default: current year)")
    parser.add_argument("-c",
                        "--code",
                        type=int,
                        default=UQCS_LEADERBOARD,
                        help="Leaderboard code (default: UQCS leaderboard)")
    parser.add_argument("-s",
                        "--sort",
                        default=SortMode.PART_2,
                        type=SortMode,
                        choices=(SortMode.PART_1, SortMode.PART_2,
                                 SortMode.DELTA),
                        help="Sorting method when displaying one day" +
                        " (default: part 2 completion time)")
    parser.add_argument("-h",
                        "--help",
                        action="store_true",
                        help="Prints this help message")

    # used to propagate usage errors out.
    # somewhat hacky. typically, this should be done by subclassing ArgumentParser
    def usage_error(message, *args, **kwargs):
        raise UsageSyntaxException(message)

    parser.error = usage_error  # type: ignore

    args = parser.parse_args(argv)

    if args.help:
        raise UsageSyntaxException("```\n" + parser.format_help() + "\n```")

    return args
Exemple #8
0
def handle_events(command: Command):
    '''
    `!events [full|all|NUM EVENTS|<NUM WEEKS> weeks]` - Lists all the UQCS
    events that are scheduled to occur within the given filter. If unspecified,
    will return the next 2 weeks of events.
    '''
    event_filter = EventFilter.from_command(command)
    if not event_filter.is_valid:
        raise UsageSyntaxException()

    http_response = requests.get(CALENDAR_URL)
    cal = Calendar.from_ical(http_response.content)

    current_time = datetime.now(tz=BRISBANE_TZ).astimezone(utc)

    events = []
    # subcomponents are how icalendar returns the list of things in the calendar
    for c in cal.subcomponents:
        # TODO: support recurring events
        # we are only interested in ones with the name VEVENT as they are events
        # we also currently filter out recurring events
        if c.name != 'VEVENT' or c.get('RRULE') is not None:
            continue

        # we convert it to our own event class
        event = Event.from_cal_event(c)
        # then we want to filter out any events that are not after the current time
        if event.start > current_time:
            events.append(event)

    # then we apply our event filter as generated earlier
    events = event_filter.filter_events(events, current_time)
    # then, we sort the events by date
    events = sorted(events, key=lambda e: e.start)

    # then print to the user the result
    if not events:
        message = f"_{event_filter.get_no_result_msg()}_\r\n" \
                  f"For a full list of events, visit: https://uqcs.org.au/calendar.html"
    else:
        message = f"{event_filter.get_header()}\r\n" + '\r\n'.join(
            str(e) for e in events)

    bot.post_message(command.channel_id, message)
Exemple #9
0
def handle_wiki(command: Command):
    """
    `!wiki <TOPIC>` - Returns a snippet of text from a relevent wikipedia entry.
    """
    if not command.has_arg():
        raise UsageSyntaxException()

    search_query = command.arg
    api_url = f"https://en.wikipedia.org/w/api.php?action=opensearch&format=json&limit=2"

    http_response = requests.get(api_url, params={'search': search_query})
    if http_response.status_code != requests.codes.ok:
        bot.post_message(command.channel_id, "Problem fetching data")
        return

    _, title_list, snippet_list, url_list = json.loads(http_response.content)

    # If the results are empty let them know. Any list being empty signifies this.
    if len(title_list) == 0:
        bot.post_message(command.channel_id, "No Results Found.")
        return

    title, snippet, url = title_list[0], snippet_list[0], url_list[0]

    # Sometimes the first element is an empty string which is weird so we handle that rare case here
    if len(title) * len(snippet) * len(url) == 0:
        bot.post_message(command.channel_id,
                         "Sorry, there was something funny about the result")
        return

    # Detect if there are multiple references to the query
    # if so, use the first reference (i.e. the second item in the lists).
    multiple_reference_instances = ("may refer to:",
                                    "may have several meanings:")
    if any(instance in snippet for instance in multiple_reference_instances):
        title, snippet, url = title_list[1], snippet_list[1], url_list[1]

    # The first url and title matches the first snippet containing any content
    message = f'{title}: {snippet}\nLink: {url}'
    bot.post_message(command.channel_id, message)
Exemple #10
0
def handle_yt(command: Command):
    '''
    `!yt <QUERY>` - Returns the top video search result based on the query string.
    '''
    # Makes sure the query is not empty.
    if not command.has_arg():
        raise UsageSyntaxException()

    search_query = command.arg.strip()
    try:
        videoID = get_top_video_result(search_query, command.channel_id)
    except HttpError as e:
        # Googleapiclient should handle http errors
        bot.logger.error(
            f'An HTTP error {e.resp.status} occurred:\n{e.content}')
        # Force return to ensure no message is sent.
        return

    if videoID:
        bot.post_message(command.channel_id, f'{YOUTUBE_VIDEO_URL}{videoID}')
    else:
        bot.post_message(command.channel_id, "Your query returned no results.")
Exemple #11
0
def handle_wolfram(command: Command):
    """
    `!wolfram [--full] <QUERY>` - Returns the wolfram response for the
    given query. If `--full` is specified, will return the full reponse.
    """
    if not command.has_arg():
        raise UsageSyntaxException()

    # Determines whether to use the full version or the short version. The full
    # version is used if the --full. argument is supplied before or after the
    # search query. See wolfram_full and wolfram_normal for the differences.
    cmd = command.arg.strip()
    # Doing it specific to the start and end just in case someone
    # has --full inside their query for whatever reason.
    if cmd.startswith('--full'):
        cmd = cmd[len('--full'):]  # removes the --full
        wolfram_full(cmd, command.channel_id)
    elif cmd.endswith('--full'):
        cmd = cmd[:-len('--full')]  # removes the --full
        wolfram_full(cmd, command.channel_id)
    else:
        wolfram_normal(cmd, command.channel_id)
Exemple #12
0
def handle_acronym(command: Command):
    '''
    `!acro <TEXT>` - Finds an acronym for the given text.
    '''
    if not command.has_arg():
        raise UsageSyntaxException()

    words = command.arg.split(" ")

    # Requested by @wbo, do not remove unless you get his express permission
    if len(words) == 1:
        word = words[0]
        if word.lower() in [":horse:", "horse"]:
            bot.post_message(command.channel_id, ">:taco:")
            return
        elif word.lower() in [":rachel:", "rachel"]:
            bot.post_message(command.channel_id, ">:older_woman:")
            return

    loop = bot.get_event_loop()
    acronym_futures = [
        get_acronyms(loop, word) for word in words[:ACRONYM_LIMIT]
    ]
    response = ""
    for word, acronyms in loop.run_until_complete(
            asyncio.gather(*acronym_futures)):
        if acronyms:
            acronym = acronyms[0]
            response += f">{word.upper()}: {acronym}\r\n"
        else:
            response += f"{word.upper()}: No acronyms found!\r\n"

    if len(words) > ACRONYM_LIMIT:
        response += f">I am limited to {ACRONYM_LIMIT} acronyms at once"

    bot.post_message(command.channel_id, response)
Exemple #13
0
 def usage_error(message, *args, **kwargs):
     raise UsageSyntaxException(message)
Exemple #14
0
def handle_events(command: Command):
    """
    `!events [full|all|NUM EVENTS|<NUM WEEKS> weeks] [uqcs|itee]`
    - Lists all the UQCS and/or  ITEE events that are
    scheduled to occur within the given filter.
    If unspecified, will return the next 2 weeks of events.
    """

    argument = command.arg if command.has_arg() else ""

    source_get = {"uqcs": False, "itee": False}
    for k in source_get:
        if k in argument:
            source_get[k] = True
            argument = argument.replace(k, "")
    argument = argument.strip()
    if not any(source_get.values()):
        source_get = dict.fromkeys(source_get, True)

    event_filter = EventFilter.from_argument(argument)
    if not event_filter.is_valid:
        raise UsageSyntaxException()

    cal = Calendar.from_ical(get_calendar_file())
    current_time = get_current_time()

    events = []
    # subcomponents are how icalendar returns the list of things in the calendar
    if source_get["uqcs"]:
        for c in cal.subcomponents:
            # TODO: support recurring events
            # we are only interested in ones with the name VEVENT as they
            # are events we also currently filter out recurring events
            if c.name != 'VEVENT' or c.get('RRULE') is not None:
                continue

            # we convert it to our own event class
            event = Event.from_cal_event(c)
            # then we want to filter out any events that are not after the current time
            if event.start > current_time:
                events.append(event)

    if source_get["itee"]:
        try:
            # Try to include events from the ITEE seminars page
            seminars = get_seminars()
            for seminar in seminars:
                # The ITEE website only lists current events.
                event = Event.from_seminar(seminar)
                events.append(event)
        except (HttpException, InvalidFormatException) as e:
            bot.logger.error(e.message)

    # then we apply our event filter as generated earlier
    events = event_filter.filter_events(events, current_time)
    # then, we sort the events by date
    events = sorted(events, key=lambda event_: event_.start)

    attachments = []
    if not events:
        message_text = f"_{event_filter.get_no_result_msg()}_\n" \
                       f"For a full list of events, visit: " \
                       f"https://uqcs.org.au/calendar.html " \
                       f"and https://www.itee.uq.edu.au/seminar-list"

    else:
        for event in events:
            color = "#5297D1" if event.source == "UQCS" else "#51237A"
            attachments.append(
                Attachment(SectionBlock(str(event)), color=color))
        message_text = f"_{event_filter.get_header()}_"

    bot.post_message(
        command.channel_id,
        text=message_text,
        attachments=[attachment._resolve() for attachment in attachments])
Exemple #15
0
 def usage_error(*args, **kwargs):
     raise UsageSyntaxException()