def handle_repo(command: Command): """ `!repo` - Returns the url for the uqcsbot Github repository and other club repos """ # Setup for message passing channel = bot.channels.get(command.channel_id) # Read the commands provided arguments = command.arg.split() if command.has_arg() else [] # All repos if len(arguments) > 0 and arguments[0] in ["--list", "-l", "list", "full", "all"]: return bot.post_message(channel, "_Useful :uqcs: Github repositories_:\n" + format_repo_message(list(REPOS.keys()))) # List of specific repos if len(arguments) > 0: return bot.post_message(channel, "_Requested :uqcs: Github repositories_:\n" + format_repo_message(arguments)) # Default option: just uqcsbot link return bot.post_message(channel, "_Have you considered contributing to the bot?_\n" + format_repo_message(["uqcsbot"]) + "\n _For more repositories, try_ `!repo list`")
def handle_dominos(command: Command): """ `!dominos [--num] N [--expiry] <KEYWORDS>` Returns a list of dominos coupons (default: 5 | max: 10) """ command_args = command.arg.split() if command.has_arg() else [] parser = argparse.ArgumentParser() def usage_error(*args, **kwargs): raise UsageSyntaxException() parser.error = usage_error # type: ignore parser.add_argument('-n', '--num', default=5, type=int) parser.add_argument('-e', '--expiry', action='store_true') parser.add_argument('keywords', nargs='*') args = parser.parse_args(command_args) coupons_amount = min(args.num, MAX_COUPONS) coupons = get_coupons(coupons_amount, args.expiry, args.keywords) message = "" for coupon in coupons: message += f"Code: *{coupon.code}* - {coupon.description}\n" bot.post_message(command.channel_id, message)
async def handle_acronym(command: Command): if not command.has_arg(): return 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, ">:taco:") return if word.lower() in [":rachel:", "rachel"]: bot.post_message(command.channel, ">:older_woman:") return acronym_futures = [get_acronyms(word) for word in words[:ACRONYM_LIMIT]] response = "" for word, acronyms in await 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, response)
def handle_calendar(command: Command): ''' `!calendar <COURSE CODE 1> [COURSE CODE 2] ...` - Returns a compiled calendar containing all the assessment for a given list of course codes. ''' channel = bot.channels.get(command.channel_id) course_names = command.arg.split() if command.has_arg() else [channel.name] if len(course_names) > COURSE_LIMIT: bot.post_message(channel, f'Cannot process more than {COURSE_LIMIT} courses.') return try: assessment = get_course_assessment(course_names) except HttpException as e: bot.logger.error(e.message) bot.post_message(channel, f'An error occurred, please try again.') return except (CourseNotFoundException, ProfileNotFoundException) as e: bot.post_message(channel, e.message) return user_direct_channel = bot.channels.get(command.user_id) bot.api.files.upload( title='Importable calendar containing your assessment!', channels=user_direct_channel.id, filetype='text/calendar', filename='assessment.ics', file=get_calendar(assessment))
def handle_mock(command: Command): """ `!mock ([TEXT] | [NUM POSTS])` - Mocks the message from the specified number of messages back. If no number is specified, mocks the most recent message. """ num_posts_back = None # Add 1 here to account for the calling user's message, # which we don't want to mock by default. if not command.has_arg(): num_posts_back = 1 elif is_number(command.arg): num_posts_back = int(command.arg) + 1 if num_posts_back is None: response = mock_message(command.arg) elif num_posts_back > MAX_NUM_POSTS_BACK: response = f'Cannot recall messages that far back, try under {MAX_NUM_POSTS_BACK}.' elif num_posts_back < 0: response = 'Cannot mock into the future (yet)!' else: message_to_mock = get_nth_most_recent_message(command.channel_id, num_posts_back) if message_to_mock is None: response = 'Something went wrong (likely insufficient conversation history).' else: response = mock_message(message_to_mock) command.reply_with(bot, response)
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)
def handle_umart(command: Command): """ `!umart <QUERY>` - Returns 5 top results for products from umart matching the search query. """ # Makes sure the query is not empty if not command.has_arg(): bot.post_message(command.channel_id, NO_QUERY_MESSAGE) return search_query = command.arg.strip() # Detects if user is being smart if "SOMETHING NOT AS SPECIFIC" in search_query: bot.post_message(command.channel_id, "Not literally...") return search_results = get_umart_results(search_query) if search_results is None: bot.post_message(command.channel_id, ERROR_MESSAGE) return if len(search_results) == 0: bot.post_message(command.channel_id, NO_RESULTS_MESSAGE) return message = "```" for result in search_results: message += f"Name: <{UMART_PRODUCT_URL}{result['link']}|{result['name']}>\n" message += f"Price: {result['price']}\n" message += "```" bot.post_message(command.channel_id, message)
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)
def handle_binify(command: Command): """ `!binify (binary | ascii)` - Converts a binary string to an ascii string or vice versa """ if not command.has_arg(): response = "Please include string to convert." elif set(command.arg).issubset(["0", "1"]) and len(command.arg) > 2: if len(command.arg) % 8 != 0: response = "Binary string contains partial byte." else: response = "" for i in range(0, len(command.arg), 8): n = int(command.arg[i:i + 8], 2) if n >= 128: response = "Character out of ascii range (0-127)" break response += chr(n) else: response = "" for c in command.arg.replace("&", "&").replace("<", "<").replace(">", ">"): n = ord(c) if n >= 128: response = "Character out of ascii range (0-127)" break response += f"{n:08b}" command.reply_with(bot, response)
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(): bot.post_message(command.channel_id, "Please specify a word") return 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}")
def handle_latex_cmd(command: Command): """ `!latex CONTENT` - Renders `CONTENT` to LaTeX and sends it to Slack. `$$ CONTENT $$` also works. """ if not command.has_arg(): bot.post_message(command.channel_id, "No data provided") handle_latex_internal(command.channel_id, command.arg.strip())
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())
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)
def handle_crates(command: Command): """ `!crates [-h] [[name] | {search,categories,users}]` - Get information about crates from crates.io """ args = parse_arguments(command.arg if command.has_arg() else '') # Executes the function that was stored by the arg parser depending on which sub-command was used args.execute_action(command.channel_id, args) # type: ignore
def handle_pastexams(command: Command): ''' `!pastexams [COURSE CODE]` - Retrieves past exams for a given course code. If unspecified, will attempt to find the ECP for the channel the command was called from. ''' channel = bot.channels.get(command.channel_id) course_code = command.arg if command.has_arg() else channel.name bot.post_message(channel, get_past_exams(course_code))
def handle_leet(command: Command) -> None: """ `!leet [`easy` | `medium` | `hard`] - Retrieves a set of questions from online coding websites, and posts in channel with a random question from this set. If a difficulty is provided as an argument, the random question will be restricted to this level of challenge. Else, a random difficulty is generated to choose. """ was_random = True # Used for output later if command.has_arg(): if (command.arg not in {"easy", "medium", "hard"}): bot.post_message(command.channel_id, "Usage: !leet [`easy` | `medium` | `hard`]") return else: difficulty = command.arg.lower() was_random = False else: difficulty = random.choice( LC_DIFFICULTY_MAP) # No difficulty specified, randomly generate # List to store questions collected questions: List[Tuple[str, str]] = [] # Go fetch questions from APIs collect_questions(questions, difficulty) selected_question = select_question(questions) # Get a random question # If we didn't find any questions for this difficulty, try again, probably timeout on all 3 if (selected_question is None): bot.post_message( command.channel_id, "Hmm, the internet pipes are blocked. Try that one again.") return # Leetcode difficulty colors color = COLORS[difficulty] if (was_random): title_text = f"Random {difficulty} question generated!" else: # Style this a bit nicer difficulty = difficulty.title() title_text = f"{difficulty} question generated!" difficulty = difficulty.title( ) # If we haven't already (i.e. random question) msg_text = f"Here's a new question for you! <{selected_question[1]}|{selected_question[0]}>" bot.post_message(command.channel_id, text=title_text, attachments=[ Attachment(SectionBlock(msg_text), color=color)._resolve() ])
def advent(command: Command) -> None: """ !advent - Prints the Advent of Code private leaderboard for UQCS """ channel = bot.channels.get(command.channel_id, use_cache=False) def reply(message): bot.post_message(channel, message, thread_ts=command.thread_ts) try: args = parse_arguments( command.arg.split() if command.has_arg() else []) except UsageSyntaxException as error: reply(str(error)) return try: leaderboard = get_leaderboard(args.year, args.code) except ValueError: reply( "Error fetching leaderboard data. Check the leaderboard code, year, and day." ) raise try: members = [ Member.from_member_data(data, args.year, args.day) for data in leaderboard["members"].values() ] except Exception: reply("Error parsing leaderboard data.") raise # whether to show only one day is_day = bool(args.day) # whether to use global points is_global = args.global_ # header message message = f":star: *Advent of Code Leaderboard {args.code}* :trophy:" if is_day: message += f"\n:calendar: *Day {args.day}* (sorted by {SORT_LABELS[args.sort]})" elif is_global: message += "\n:earth_asia: *Global Leaderboard Points*" # reply with leaderboard as a file attachment because it gets quite large. bot.api.files.upload( initial_comment=message, content=format_advent_leaderboard(members, is_day, is_global, args.sort), title=f"advent_{args.code}_{args.year}_{args.day}.txt", filetype="text", channels=channel.id, thread_ts=command.thread_ts)
def handle_zalgo(command: Command): """ `!zalgo TEXT` - Adds Zalgo characters to the given text. """ text = command.arg if command.has_arg() else "Cthulhu fhtagn!" response = "" for c in text: response += c for i in range(randrange(7)//3): response += choice(HORROR) bot.post_message(command.channel_id, response)
def handle_cards(command: Command): """ `!cards [number] [joker]` - Deals one or more cards """ # easter egg - prepare four 500 hands, and the kitty if command.arg == "500": deck = (list(range(0, 0 + 10)) + list(range(13, 13 + 11)) + list(range(26, 26 + 10)) + list(range(39, 39 + 11)) + [-1]) shuffle(deck) hands = [deck[0:10], deck[10:20], deck[20:30], deck[30:40], deck[40:]] for i in range(5): h = [emojify(j) for j in sorted(hands[i])] response = [ ":regional-indicator-n: ", ":regional-indicator-e: ", ":regional-indicator-s: ", ":regional-indicator-w: ", ":cat: " ][i] + "".join(h) bot.post_message(command.channel_id, response) return deck = list(range(52)) # add joker if command.has_arg() and command.arg.split(" ")[-1][0].lower() == "j": deck.append(-1) # set number to deal if command.has_arg() and command.arg.split(" ")[0].isnumeric(): cards = min(max(int(command.arg.split(" ")[0]), 1), len(deck)) else: cards = 1 shuffle(deck) deck = deck[:cards] deck.sort() response = "" for i in deck: response += emojify(i) bot.post_message(command.channel_id, response)
def handle_whatsdue(command: Command): """ `!whatsdue [-f] [--full] [COURSE CODE 1] [COURSE CODE 2] ...` - Returns all the assessment for a given list of course codes that are scheduled to occur after today. If unspecified, will attempt to return the assessment for the channel that the command was called from. If -f/--full is provided, will return the full assessment list without filtering by cutoff dates. """ channel = bot.channels.get(command.channel_id) command_args = command.arg.split() if command.has_arg() else [] is_full_output = False if '--full' in command_args: command_args.remove('--full') is_full_output = True if '-f' in command_args: command_args.remove('-f') is_full_output = True # If we have any command args left, they're course names. If we don't, # attempt to instead use the channel name as the course name. course_names = command_args if len(command_args) > 0 else [channel.name] if len(course_names) > COURSE_LIMIT: bot.post_message(channel, f'Cannot process more than {COURSE_LIMIT} courses.') return # If full output is not specified, set the cutoff to today's date. cutoff = None if is_full_output else datetime.today() try: asses_page = get_course_assessment_page(course_names) assessment = get_course_assessment(course_names, cutoff, asses_page) except HttpException as e: bot.logger.error(e.message) bot.post_message(channel, f'An error occurred, please try again.') return except (CourseNotFoundException, ProfileNotFoundException) as e: bot.post_message(channel, e.message) return message = ( '_*WARNING:* Assessment information may vary/change/be entirely' + ' different! Use at your own discretion_\n>>>') message += '\n'.join(map(get_formatted_assessment_item, assessment)) if not is_full_output: message += ( '\n_Note: This may not be the full assessment list. Use -f' + '/--full to print out the full list._') message += f'\nLink to assessment page <{asses_page}|here>' bot.post_message(channel, message)
def from_command(cls, command: Command): if not command.has_arg(): return cls(weeks=2) else: match = re.match(FILTER_REGEX, command.arg) if not match: return cls(is_valid=False) filter_str = match.group(0) if filter_str in ['full', 'all']: return cls(full=True) elif 'week' in filter_str: return cls(weeks=int(filter_str.split()[0])) else: return cls(cap=int(filter_str))
def handle_coin(command: Command): """ `!coin [number]` - Flips 1 or more coins. """ if command.has_arg() and command.arg.isnumeric(): flips = min(max(int(command.arg), 1), 500) else: flips = 1 response = [] emoji = (':heads:', ':tails:') for i in range(flips): response.append(choice(emoji)) bot.post_message(command.channel_id, "".join(response))
def handle_parking(command: Command) -> None: """ `!parking [all]` - Displays how many car parks are available at UQ St. Lucia By default, only dispalys casual parking availability """ if command.has_arg() and command.arg.lower() == "all": permit = True else: permit = False # read parking data code, data = get_pf_parking_data() if code != 200: bot.post_message(command.channel_id, "Could Not Retrieve Parking Data") return response = ["*Available Parks at UQ St. Lucia*"] names = {"P1": "P1 - Warehouse (14P Daily)", "P2": "P2 - Space Bank (14P Daily)", "P3": "P3 - Multi-Level West (Staff)", "P4": "P4 - Multi-Level East (Staff)", "P6": "P6 - Hartley Teakle (14P Hourly)", "P7": "P7 - DustBowl (14P Daily)", "P7 UC": "P7 - Keith Street (14P Daily Capped)", "P8 L1": "P8 - Athletics Basement (14P Daily)", "P8 L2": "P8 - Athletics Roof (14P Daily)", "P9": "P9 - Boatshed (14P Daily)", "P10": "P10 - UQ Centre & Playing Fields (14P Daily/14P Daily Capped)", "P11 L1": "P11 - Conifer Knoll Lower (Staff)", "P11 L2": "P11 - Conifer Knoll Upper (Staff)", "P11 L3": "P11 - Conifer Knoll Roof (14P Daily Restricted)"} def category(fill): if fill.upper() == "FULL": return "No" if fill.upper() == "NEARLY FULL": return "Few" return fill # find parks table = Soup(data, "html.parser").find("table", attrs={"id": "parkingAvailability"}) rows = table.find_all("tr")[1:] # split and join for single space whitespace areas = [[" ".join(i.get_text().split()) for i in j.find_all("td")] for j in rows] for area in areas: if area[2]: response.append(f"{category(area[2])} Carparks Available in {names[area[0]]}") elif permit and area[1]: response.append(f"{category(area[1])} Carparks Available in {names[area[0]]}") bot.post_message(command.channel_id, "\n".join(response))
def handle_dice(command: Command): """ `!dice [number]` - Rolls 1 or more six sided dice (d6). """ if command.has_arg() and command.arg.isnumeric(): rolls = min(max(int(command.arg), 1), 360) else: rolls = 1 response = [] emoji = (':dice-one:', ':dice-two:', ':dice-three:', ':dice-four:', ':dice-five:', ':dice-six:') for i in range(rolls): response.append(choice(emoji)) bot.post_message(command.channel_id, "".join(response))
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}')
def handle_urban(command: Command) -> None: """ `!urban <PHRASE>` - Looks a phrase up on Urban Dictionary. """ # Check for search phase if not command.has_arg(): bot.post_message(command.channel_id, '> Usage: `!urban <SEARCH_PHRASE>`') return 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.') return results = http_response.json() # If search phrase is not found, notify user. if results['result_type'] != 'exact': bot.post_message(command.channel_id, f'> No results found for {search_term}. ¯\\_(ツ)_/¯') return # Get best definition (by most 'thumbs up') and construct response. sorted_defs = sorted(results['list'], key=lambda def_: def_['thumbs_up'], reverse=True) best_def = sorted_defs[0] # Break example into individual lines and wrap each in it's own block quote. example = best_def.get('example', '').split('\r\n') formatted_example = '\n'.join(f'> {line}' for line in example) # Format message and send response to user in channel query was sent from. message = f'*{search_term.title()}*\n' \ f'{best_def["definition"].capitalize()}\n' \ f'{formatted_example}' # Only link back to Urban Dictionary if there are more definitions. if len(sorted_defs) > 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)
def handle_help(command: Command): """ `!help [COMMAND]` - Display the helper docstring for the given command. If unspecified, will return the helper docstrings for all commands. """ # If a command was specified, only grab its helper docstring. Else, grab all # helper docstrings. helper_docs = [ sanitize_doc(helper_doc) for command_name, helper_doc in get_helper_docs() if not command.has_arg() or command.arg == command_name ] if len(helper_docs) == 0: message = f'Could not find any helper docstrings.' else: message = '>>>' + '\n'.join(helper_docs) user_direct_channel = bot.channels.get(command.user_id) bot.post_message(user_direct_channel, message)
def handle_ecp(command: Command): ''' `!ecp [COURSE CODE]` - Returns the link to the latest ECP for the given course code. If unspecified, will attempt to find the ECP for the channel the command was called from. ''' channel = bot.channels.get(command.channel_id) course_name = channel.name if not command.has_arg() else command.arg try: profile_url = get_course_profile_url(course_name) except HttpException as e: bot.logger.error(e.message) bot.post_message(channel, f'An error occurred, please try again.') return except (CourseNotFoundException, ProfileNotFoundException) as e: bot.post_message(channel, e.message) return bot.post_message(channel, f'*{course_name}*: <{profile_url}|ECP>')
def handle_xkcd(command: Command) -> None: """ `!xkcd [COMIC_ID|SEARCH_PHRASE]` - Returns the xkcd comic associated with the given COMIC_ID (an integer) or matching the SEARCH_PHRASE. Providing no arguments will return the most recent comic. """ if command.has_arg(): argument = command.arg if is_id(argument): comic_number = int(argument) response = get_by_id(comic_number) else: response = get_by_search_phrase(command.arg) else: response = get_latest() bot.post_message(command.channel_id, response, unfurl_links=True, unfurl_media=True)
def handle_hoogle(command: Command): """ `!hoogle [-v] [--verbose] <TYPE_SIGNATURE>` - Queries the Hoogle Haskell API search engine, searching Haskell libraries by either function name, or by approximate type signature. """ command_args = command.arg.split() if command.has_arg() else [] verbose = False if '--verbose' in command_args: command_args.remove('--verbose') verbose = True if '-v' in command_args: command_args.remove('-v') verbose = True if len(command_args) == 0: bot.post_message(command.channel_id, "usage: " + handle_hoogle.__doc__) return type_sig = ' '.join(command_args) endpoint_url = get_endpoint(type_sig) http_response = requests.get(endpoint_url) if http_response.status_code != requests.codes.ok: bot.post_message(command.channel_id, "Problem fetching data") return results = json.loads(http_response.content) if len(results) == 0: bot.post_message(command.channel_id, "No results found") return message = "\n".join( pretty_hoogle_result(result, verbose) for result in results) bot.post_message(command.channel_id, message)