async def cog_before_invoke(self, ctx): if Sudo.is_authorized_command(self.bot, ctx): old_userSettings = deepcopy(self.bot.userSettings) self.bot.userSettings = Sudo.user_settings_check( self.bot.userSettings, ctx.author.id) if old_userSettings != self.bot.userSettings: Sudo.save_configs(self.bot) #Leveling system self.bot.userSettings[ctx.author.id]['level']['xp'] += 1 if self.bot.userSettings[ ctx.author.id]['level']['xp'] >= self.bot.userSettings[ ctx.author.id]['level']['rank'] * 10: self.bot.userSettings[ctx.author.id]['level']['xp'] = 0 self.bot.userSettings[ctx.author.id]['level']['rank'] += 1 Sudo.save_configs(self.bot) await ctx.send(embed=Embed( description= f"Congratulations {ctx.author}, you are now level {self.bot.userSettings[ctx.author.id]['level']['rank']}" )) # if ctx.command.name == 's' and self.bot.userSettings[ctx.author.id]['searchAlias'] is not None: # Log.append_to_log(ctx, self.bot.userSettings[ctx.author.id]['searchAlias']) # elif ctx.command.name == 's': # Log.append_to_log(ctx, 's', 'Not set') # else: Log.append_to_log(ctx) return
async def sudo(self, ctx, *args): if Sudo.is_sudoer(self.bot, ctx): Log.append_to_log(ctx, None, args) command = Sudo(self.bot, ctx) self.bot.serverSettings, self.bot.userSettings = await command.sudo( list(args)) Sudo.save_configs(self.bot) else: await ctx.send( f"`{ctx.author}` is not in the sudoers file. This incident will be reported." ) Log.append_to_log(ctx, None, 'unauthorised') return
async def invite(self, ctx): try: Log.append_to_log(ctx) dm = await ctx.author.create_dm() await dm.send( discord.utils.oauth_url( client_id=self.bot.botuser.id, permissions=discord.Permissions(4228381776), scopes=['bot', 'applications.commands'])) except discord.errors.Forbidden: await ctx.send( 'Sorry, I cannot open a DM at this time. Please check your privacy settings' ) except Exception as e: await error_handler(self.bot, ctx, e) finally: return
async def config(self, ctx, *args): args = list(args) command = Sudo(self.bot, ctx) if len(args) > 0: localSetting = args[0] in ['locale', 'alias'] else: localSetting = False if Sudo.is_sudoer(self.bot, ctx) or localSetting: self.bot.serverSettings, self.bot.userSettings = await command.config( args) else: self.bot.serverSettings, self.bot.userSettings = await command.config( []) Sudo.save_configs(self.bot) Log.append_to_log(ctx)
async def __call__(self): UserCancel = KeyboardInterrupt def search_pages(result_) -> discord.Embed: return discord.Embed( title=f"Titles matching '{self.query}'", description="\n".join([ f"[{index_}]: {value.title}" for index_, value in enumerate(result_) ]), ) try: errorMessage = None search = AnimeSearch(self.query) while 1: result = [ [anime for anime in search.results][x:x + 10] for x in range(0, len([anime for anime in search.results]), 10) ] embeds = list(map(search_pages, result)) cur_page = 0 for index, item in enumerate(embeds): item.set_footer( text= f"Please choose option or cancel\nPage {index+1}/{len(embeds)}" ) if len(embeds) > 1: emojis = ["🗑️", "◀️", "▶️"] else: emojis = ["🗑️"] await self.message.edit(content='', embed=embeds[cur_page % len(embeds)], components=[[ Button(style=ButtonStyle.blue, label=e, custom_id=e) for e in emojis ]]) while 1: try: emojitask = asyncio.create_task( self.bot.wait_for( "button_click", check=lambda b_ctx: Sudo.pageTurnCheck( bot=self.bot, ctx=self.ctx, button_ctx=b_ctx, message=self.message), timeout=60, )) responsetask = asyncio.create_task( self.bot.wait_for( "message", check=lambda m: m.author == self.ctx.author, timeout=30, )) waiting = [emojitask, responsetask] done, waiting = await asyncio.wait( waiting, return_when=asyncio.FIRST_COMPLETED ) # 30 seconds wait either reply or react if emojitask in done: emojitask = emojitask.result() if str(emojitask.custom_id) == "🗑️": await self.message.delete() return elif str(emojitask.custom_id) == "◀️": cur_page -= 1 elif str(emojitask.custom_id) == "▶️": cur_page += 1 await emojitask.respond(type=7, content='', embed=embeds[cur_page % len(embeds)]) elif responsetask in done: try: emojitask.cancel() input = responsetask.result() await input.delete() if input.content.lower() == "cancel": raise UserCancel input = int(input.content) anime_item = result[cur_page][input] if errorMessage is not None: await errorMessage.delete() embed = discord.Embed( title=f"{anime_item.title}", description=anime_item.synopsis, url=anime_item.url, ) # Myanimelist data embed.add_field( name="MyAnimeListID", value=str(anime_item.mal_id), inline=True, ) embed.add_field( name="Rating", value=str(anime_item.score), inline=True, ) embed.add_field( name="Episodes", value=str(anime_item.episodes), inline=True, ) embed.set_thumbnail(url=anime_item.image_url) embed.set_footer( text=f"Requested by {self.ctx.author}") Log.append_to_log( self.ctx, f"{self.ctx.command} result", anime_item.title) await self.message.edit( embed=embed, components=[ Button(style=ButtonStyle.blue, label="🗑️", custom_id="🗑️") ]) await self.bot.wait_for( "button_click", check=lambda button_ctx: Sudo. pageTurnCheck(bot=self.bot, ctx=self.ctx, button_ctx=button_ctx, message=self.message), timeout=60, ) return except (ValueError, IndexError): errorMessage = await self.ctx.send( content= "Invalid choice. Please choose a number between 0-9 or cancel" ) continue except asyncio.TimeoutError: await self.message.edit(components=[]) return except UserCancel: await self.message.delete() except asyncio.TimeoutError: await self.message.delete() except (asyncio.CancelledError, discord.errors.NotFound): pass except Exception: await self.message.delete() raise except Exception as e: await error_handler(self.bot, self.ctx, e, self.query) finally: return
async def __call__(self): UserCancel = KeyboardInterrupt # region various embed types creation def publication_embeds(result) -> discord.Embed: embed = discord.Embed( title=result["bib"]["title"], description=result["bib"]["abstract"], url=result["eprint_url"] if "eprint_url" in result.keys() else result["pub_url"], ) embed.add_field( name="Authors", value=", ".join(result["bib"]["author"]).strip(), inline=True, ) embed.add_field(name="Publisher", value=result["bib"]["venue"], inline=True) embed.add_field( name="Publication Year", value=result["bib"]["pub_year"], inline=True ) embed.add_field( name="Cited By", value=result["num_citations"] if "num_citations" in result.keys() else "0", inline=True, ) embed.add_field( name="Related Articles", value=f'https://scholar.google.com{result["url_related_articles"]}', inline=True, ) embed.set_footer(text=f"Requested by {self.ctx.author}") return embed def author_embeds(result) -> discord.Embed: embed = discord.Embed(title=result["name"]) embed.add_field( name="Cited By", value=f"{result['citedby']} articles", inline=True ) embed.add_field(name="Scholar ID", value=result["scholar_id"], inline=True) embed.add_field( name="Affiliation", value=result["affiliation"] if "affiliation" in result.keys() else "None", inline=True, ) embed.add_field( name="Interests", value=f"{', '.join(result['interests']) if 'interests' in result.keys() else 'None'}", inline=True, ) embed.set_image(url=result["url_picture"]) embed.set_footer(text=f"Requested by {self.ctx.author}") return embed def citation_embeds(result) -> discord.Embed: embed = discord.Embed( title=result["bib"]["title"], description=f"```{scholarly.bibtex(result)}```", url=result["eprint_url"] if "eprint_url" in result.keys() else result["pub_url"], ) embed.set_footer(text=f"Requested by {self.ctx.author}") return embed # endregion try: # region user flags processing pg = ProxyGenerator() proxy = FreeProxy(rand=True, timeout=1, country_id=["BR"]).get() pg.SingleProxy(http=proxy, https=proxy) scholarly.use_proxy(pg) # self.args processing if self.args is None: results = [next(scholarly.search_pubs(self.query)) for _ in range(5)] embeds = list(map(publication_embeds, results)) elif "author" in self.args: results = [ next(scholarly.search_author(self.query)) for _ in range(5) ] embeds = list(map(author_embeds, results)) elif "cite" in self.args: results = scholarly.search_pubs(self.query) results = [results for _ in range(5)] embeds = list(map(citation_embeds, results)) else: await self.message.edit(content="Invalid flag") return # endregion # sets the reactions for the search result if len(embeds) > 1: buttons = [[ {Button(style=ButtonStyle.grey, label="◀️", custom_id="◀️"): None}, {Button(style=ButtonStyle.red, label="🗑️", custom_id="🗑️"): None}, {Button(style=ButtonStyle.grey, label="▶️", custom_id="▶️"): None} ]] else: buttons = [[ Button(style=ButtonStyle.red, label="🗑️", custom_id="🗑️") ]] await Sudo.multi_page_system(self.bot, self.ctx, self.message, tuple(embeds), buttons) return except asyncio.TimeoutError: raise except (asyncio.CancelledError, discord.errors.NotFound): pass except scholarly_exceptions._navigator.MaxTriesExceededException: await self.message.edit( content="Google Scholar is currently blocking our requests. Please try again later" ) Log.append_to_log(self.ctx, f"{self.ctx.command} error", "MaxTriesExceededException") return except Exception as e: await error_handler(self.bot, self.ctx, e, self.query) finally: return
async def genericSearch(self, ctx, searchObject, query: str, optionals: str = None): if Sudo.is_authorized_command(self.bot, ctx): old_userSettings = deepcopy(self.bot.userSettings) self.bot.userSettings = Sudo.user_settings_check( self.bot.userSettings, ctx.author.id) if old_userSettings != self.bot.userSettings: Sudo.save_configs(self.bot) #Leveling system self.bot.userSettings[ctx.author.id]['level']['xp'] += 1 if self.bot.userSettings[ ctx.author.id]['level']['xp'] >= self.bot.userSettings[ ctx.author.id]['level']['rank'] * 10: self.bot.userSettings[ctx.author.id]['level']['xp'] = 0 self.bot.userSettings[ctx.author.id]['level']['rank'] += 1 Sudo.save_configs(self.bot) await ctx.send(embed=Embed( description= f"Congratulations {ctx.author}, you are now level {self.bot.userSettings[ctx.author.id]['level']['rank']}" )) # if ctx.command.name == 's' and self.bot.userSettings[ctx.author.id]['searchAlias'] is not None: # Log.append_to_log(ctx, self.bot.userSettings[ctx.author.id]['searchAlias']) # elif ctx.command.name == 's': # Log.append_to_log(ctx, 's', 'Not set') # else: Log.append_to_log(ctx) #allows users to edit their search query after results are returned continueLoop = True while continueLoop: try: message = await ctx.send(content=get_loading_message()) messageEdit = create_task( self.bot.wait_for('message_edit', check=lambda var, m: m.author == ctx. author and m == ctx.message)) search = create_task( searchObject( bot=self.bot, ctx=ctx, server_settings=self.bot.serverSettings, user_settings=self.bot.userSettings, message=message, args=optionals.split(' ') if optionals is not None else [], query=query, )()) #checks for message edit waiting = [messageEdit, search] done, waiting = await wait( waiting, return_when=asyncio.FIRST_COMPLETED) if messageEdit in done: #if the message is edited, the search is cancelled, message deleted, and command is restarted if type(messageEdit.exception()) == TimeoutError: raise TimeoutError await message.delete() messageEdit.cancel() search.cancel() messageEdit = messageEdit.result() userquery = messageEdit[1].content.replace( f'{Sudo.print_prefix(self.bot.serverSettings, ctx)}{ctx.invoked_with} ', '') #finds the new user query continue else: raise TimeoutError except TimeoutError: #after a minute, everything cancels await message.edit(components=[]) messageEdit.cancel() search.cancel() continueLoop = False return except Exception as e: await error_handler(self.bot, ctx, e, userquery) return
async def google(self) -> None: # region utility functions # translates unicode codes in links def link_unicode_parse(link: str) -> str: return sub(r"%(.{2})", lambda m: chr(int(m.group(1), 16)), link) # parses image url from html def image_url_parser(image) -> str: try: # searches html for image urls imgurl = link_unicode_parse( findall("(?<=imgurl=).*(?=&imgrefurl)", image.parent.parent["href"])[0]) if "encrypted" in imgurl: imgurl = findall( "(?<=imgurl=).*(?=&imgrefurl)", image.findAll("img")[1].parent.parent["href"], )[0] return imgurl except Exception: raise # endregion # region embed creation functions # embed creation for image embeds def image_embed(image) -> Embed: try: # creates and formats the embed result_embed = Embed( title=f"Search results for: {self.query[:233]}" f'{"..." if len(self.query) > 233 else ""}') # sets the discord embed to the image result_embed.set_image(url=image_url_parser(image)) result_embed.url = self.url except: result_embed.set_image( url= "https://external-preview.redd.it/9HZBYcvaOEnh4tOp5EqgcCr_vKH7cjFJwkvw-45Dfjs.png?auto=webp&s=ade9b43592942905a45d04dbc5065badb5aa3483" ) finally: return result_embed # embed creation for text embeds def text_embed(result) -> Embed: # creates and formats the embed result_embed = Embed( title= f'Search results for: {self.query[:233]}{"..." if len(self.query) > 233 else ""}' ) # google results are separated by divs # searches for link in div find_link = result.find_all("a", href_="") link_list = tuple(a for a in find_link if not a.find("img")) link = None if len(link_list) != 0: try: # parses link from html link = link_unicode_parse( findall(r"(?<=url\?q=).*(?=&sa)", link_list[0]["href"])[0]) except: pass # extracts all meaningful text in the search result by div result_find = result.findAll("div") divs = tuple(d for d in result_find if not d.find("div")) titleinfo = [ " ".join([ string if string != "View all" else "" for string in div.stripped_strings ]) for div in divs[:2] ] titleinfo = [f"**{ti}**" for ti in titleinfo if ti != ""] if link is not None: titleinfo[-1] = link lines = [ " ".join([ string if string != "View all" else "" for string in div.stripped_strings ]) for div in divs[2:] ] printstring = "\n".join(titleinfo + lines) # discord prevents embeds longer than 2048 chars # truncates adds ellipses to strings longer than 2048 chars if len(printstring) > 1024: printstring = printstring[:1020] + "..." # sets embed description to string result_embed.description = sub("\n\n+", "\n\n", printstring) # tries to add an image to the embed image = result.find("img") try: result_embed.set_image(url=image_url_parser(image)) except: pass result_embed.url = self.url return result_embed # endregion try: # checks if image is in search query if bool(re_search("image", self.query.lower())): has_found_image = True else: has_found_image = False # gets the webscraped html of the google search async with self.bot.session.get( self.url, headers={'User-Agent': 'python-requests/2.25.1'}) as data: html = await data.text() soup, index = BeautifulSoup(html, features="lxml", parse_only=SoupStrainer( 'div', {'id': 'main'})), 3 #Debug HTML output # with open('test.html', 'w', encoding='utf-8-sig') as file: # file.write(soup.prettify()) # if the search returns results if soup.find("div", {"id": "main"}) is not None: Log.append_to_log(self.ctx, f"{self.ctx.command} results", self.url) google_snippet_results = soup.find("div", { "id": "main" }).contents # region html processing # html div cleanup google_snippet_results = [ google_snippet_results[resultNumber] for resultNumber in range(3, len(google_snippet_results) - 2) ] # bad divs to discard wrong_first_results = { "Did you mean: ", "Showing results for ", "Tip: ", "See results about", "Related searches", "Including results for ", "Top stories", "People also ask", "Next >", } # bad div filtering google_snippet_results = { result for result in google_snippet_results if not any(badResult in result.strings for badResult in wrong_first_results) or result.strings == "" } # endregion # checks if user searched specifically for images embeds = None if has_found_image: # searches for the "images for" search result div for results in google_snippet_results: if "Images" in results.strings: images = results.findAll("img", recursive=True) #checks if image wont embed properly bad_urls = [] async def http_req(index, url): try: url = image_url_parser(url) except: return async with self.bot.session.get( url, allow_redirects=False) as resp: if resp.status == 301 or resp.status == 302: bad_urls.append(index) await gather(*[ http_req(index, url) for index, url in enumerate(images) ]) if len(bad_urls) > 0: images = [ img for idx, img in enumerate(images) if idx not in bad_urls ] #creates embed list embeds = [ embed for embed in list(map(image_embed, images)) if embed.description is not (None or '') ] if len(embeds) > 0: del embeds[-1] break if embeds is None or len(embeds) == 0: embeds = [ embed for embed in list( map(text_embed, google_snippet_results)) if embed.description is not (None or '') ] #Creates search groupings new_embed_list = [] i = 0 combinedDesc = '' for j in range(len(embeds)): embed_desc = '\n'.join( list( filter(None, embeds[j].description.split('\n')))) if 'image' in embeds[j].to_dict().keys(): combinedDesc = '' new_embed_list.append([embeds[j]]) i = j continue else: if len(combinedDesc + embed_desc) < 1048: combinedDesc += '\n' + '\n' + embed_desc continue combinedDesc = '' new_embed_list.append(embeds[i:j + 1]) i = j new_embed_list.append(embeds[i:j + 1]) for idx, group in enumerate(new_embed_list): if len(group) == 1: continue combinedDesc = '' for embed in group: combinedDesc += '\n' + '\n' + '\n'.join( list( filter(None, embed.description.split('\n')))) new_embed_list[idx] = [ Embed( title= f'Search results for: {self.query[:233]}{"..." if len(self.query) > 233 else ""}', description=combinedDesc, url=self.url) ] embeds = [i[0] for i in new_embed_list] # adds the page numbering footer to the embeds for index, item in enumerate(embeds): item.set_footer( text= f"Page {index+1}/{len(embeds)}\nRequested by: {str(self.ctx.author)}" ) print(self.ctx.author.name + " searched for: " + self.query[:233]) #finds the first https link first_link = None for idx, e in enumerate(embeds): try: first_link = next( filter(lambda x: 'https://' in x, e.description.split('\n'))) break except Exception: continue # sets the buttons for the search result if len(embeds) > 1: buttons = [[{ Button(style=ButtonStyle.blue, label="Screenshot", custom_id="scr"): self.webpage_screenshot }, { Button(style=ButtonStyle.blue, label="Screenshot First Result", custom_id="scrfr"): (self.webpage_screenshot, first_link) }], [{ Button(style=ButtonStyle.grey, label="◀️", custom_id="◀️"): None }, { Button(style=ButtonStyle.red, label="🗑️", custom_id="🗑️"): None }, { Button(style=ButtonStyle.grey, label="▶️", custom_id="▶️"): None }]] else: buttons = [[{ Button(style=ButtonStyle.blue, label="Screenshot", custom_id="scr"): self.webpage_screenshot }, { Button(style=ButtonStyle.blue, label="Screenshot First Result", custom_id="scrfr"): (self.webpage_screenshot, first_link) }], [{ Button(style=ButtonStyle.red, label="🗑️", custom_id="🗑️"): None }]] #search for images button if "images" not in self.query.lower(): buttons[0].append({ Button(style=ButtonStyle.blue, label="Images", custom_id="img", emoji=self.bot.get_emoji(928889019838894090)): (self.search_google_handler, f'{self.query} images') }) await Sudo.multi_page_system(self.bot, self.ctx, self.message, tuple(embeds), buttons) else: embed = Embed( title= f'Search results for: {self.query[:233]}{"..." if len(self.query) > 233 else ""}', description= "No results found. Maybe try another search term.", ) embed.set_footer(text=f"Requested by {self.ctx.author}") buttons = [[ Button(style=ButtonStyle.red, label="🗑️", custom_id="🗑️") ]] await Sudo.multi_page_system(self.bot, self.ctx, self.message, (embed, ), buttons) except TimeoutError: raise except Exception as e: await self.message.delete() await error_handler(self.bot, self.ctx, e, self.query) finally: return
async def __call__(self): UserCancel = KeyboardInterrupt error_count = 0 while error_count <= 1: try: x = self.Xkcd(self.query) embed = discord.Embed(title=x.title, description=x.alt, timestamp=x.date) embed.url = x.url embed.set_image(url=x.img_url) embed.set_footer(text=f"Requested by {self.ctx.author}") Log.append_to_log(self.ctx, f"{self.ctx.command} result", x.url) # sets the reactions for the search result buttons = [[{ Button(style=ButtonStyle.red, label="🗑�", custom_id="🗑�"): None }]] await Sudo.multi_page_system(self.bot, self.ctx, self.message, (embed, ), buttons) return except UserCancel: await self.ctx.send(f"Cancelled") return except ValueError: error_msg = await self.ctx.send( "Invalid input, an XKCD comic number is needed. Please edit your search or try again." ) self.message_edit = asyncio.create_task( self.bot.wait_for( "message_edit", check=lambda var, m: m.author == self.ctx.author, timeout=60, )) reply = asyncio.create_task( self.bot.wait_for( "message", check=lambda m: m.author == self.ctx.author, timeout=60)) waiting = [self.message_edit, reply] done, waiting = await asyncio.wait( waiting, return_when=asyncio.FIRST_COMPLETED ) # 30 seconds wait either reply or react if self.message_edit in done: reply.cancel() self.message_edit = self.message_edit.result() self.query = "".join([ li for li in difflib.ndiff(self.message_edit[0].content, self.message_edit[1].content) if "+" in li ]).replace("+ ", "") elif reply in done: self.message_edit.cancel() reply = reply.result() await reply.delete() if reply.content == "cancel": self.message_edit.cancel() reply.cancel() break else: self.query = reply.content await error_msg.delete() error_count += 1 continue except asyncio.TimeoutError: return except (asyncio.CancelledError, discord.errors.NotFound): pass except Exception as e: await error_handler(self.bot, self.ctx, e, self.query) return
async def stock_market(self): def parse_query(query_str): '''Parses input query string of the form &stock <ticker> <range> <interval> <type> <moving average values> and returns a dictionary containing tokens if successful.''' valid_ranges = [ '1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max' ] valid_intervals = [ '1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1wk', '1mo', '3mo' ] valid_types = ['candle', 'line'] usage = '''Usage: stock <ticker> <range> <interval> <type> <moving average values>. E.g. &stock AAPL 1y 1d candle 2 3 5 creates a candlestick chart of AAPL for the last year at a daily interval with 2, 3, and 5 day moving averages.''' query_tokens = query_str.strip().split(' ') # Input checks if not (query_tokens[0] == 'stock'): raise TypeError(usage) elif not query_tokens[2] in valid_ranges: raise TypeError('The valid ranges are: ' + str(valid_ranges)) elif not query_tokens[3] in valid_intervals: raise TypeError('The valid intervals are: ' + str(valid_intervals)) elif not query_tokens[4] in valid_types: raise TypeError('The valid types are: ' + str(valid_types)) mav = query_tokens[5:] if mav: for value in mav: try: int(value) except Exception: raise TypeError( "Invalid moving average values, try again with integers." ) return { 'ticker' : query_tokens[1] \ .upper() \ .replace('$',''), 'range' : query_tokens[2], 'interval' : query_tokens[3], 'type' : query_tokens[4], 'mav' : tuple(int(x) for x in query_tokens[5:]) } def request_data(tokens): '''Sends a query to Yahoo Finance for the stock data of the token. Returns a Pandas dataframe if successful.''' stock_url = 'https://query1.finance.yahoo.com/v7/finance/download/{}?' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0' } try: # Parameters for request params = { 'range': tokens['range'], 'interval': tokens['interval'], 'events': 'history', } response = get(stock_url.format(tokens['ticker']), headers=headers, params=params) if response.status_code == 404: raise TypeError( "Error 404; please try again with another ticker/range/interval." ) # Parse response into dataframe file = StringIO(response.text) return read_csv(file, index_col=0, parse_dates=True) except Exception: raise def create_chart(data, tokens): '''Creates stock chart from the data and tokens provided. Saves chart as image if successful.''' up_color = '#59eb00' # Green down_color = '#FF0000' # Red dc_gray = '#36393E' # Discord's background gray # Sets the colors of the candlesticks, wicks, and volume bars mc = make_marketcolors(up=dc_gray, down=down_color, wick={ 'up': up_color, 'down': down_color }, edge={ 'up': up_color, 'down': down_color }, volume={ 'up': up_color, 'down': down_color }) # Sets the colors of the background, text, and moving average lines s = make_mpf_style(marketcolors=mc, base_mpl_style='dark_background', facecolor=dc_gray, figcolor=dc_gray, mavcolors=[ '#5662f6', '#FFEB00', '#FF7A00', '#AEF35A', '#028910' ]) try: # Arguments for the plot function setup = { 'type': tokens['type'], 'volume': True, 'tight_layout': True, 'style': s, 'mav': tokens['mav'], 'scale_width_adjustment': { 'lines': 0.8, 'volume': 0.65 }, 'figscale': 1, 'figratio': (10, 5), 'linecolor': '#FFFFFF' } # Plot graph _fig, axes = plot(data, **setup, returnfig=True) # Select the correct plot ax = axes[0] # Set title containing the ticker, range, and interval ax.set_title( f"{tokens['ticker']} ({tokens['range']} Range, {tokens['interval']} Interval)" ) # Loops through the plotted moving average line (if any) to get the latest MA value, then displays it on the top left, # cycling through the available colors in "mavcolors" if tokens['mav']: for i, _line in enumerate(ax.lines): # Ignores the last line if a line chart is requested since that is the actual data if tokens['type'] == 'line' and i == len(ax.lines) - 1: continue # Construct 'MA12: 345.67' string MA_string = f"MA{tokens['mav'][i]}: {ax.lines[i].get_ydata()[-1]:.2f}" # Places string with vertical offset for every MA line ax.text(0.02, 0.94 - 0.04 * i, MA_string, transform=ax.transAxes, color=s["mavcolors"][i % len(s["mavcolors"])]) # Save figure, 'tight' setting prevents labels from being cut off b = BytesIO() savefig(b, bbox_inches='tight', dpi=300) close() return b except Exception: raise try: tokens = parse_query(self.query) data = request_data(tokens) chart = create_chart(data, tokens) chart.seek(0) file = File(fp=chart, filename='image.png') embed = Embed(title=tokens['ticker']) embed.set_image(url="attachment://image.png") embed.set_footer(text=f"Requested by: {str(self.ctx.author)}") await self.message.delete() Log.append_to_log(self.ctx, f"{self.ctx.command} results", f'stock {tokens["ticker"]}') self.message = await self.ctx.send(embed=embed, file=file) emojis = {"🗑️": None} await Sudo.multi_page_system(self.bot, self.ctx, self.message, (embed, ), emojis) return except TypeError as e: await self.message.edit(content='', embed=Embed(description=e.args[0])) return