Example #1
0
    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
Example #2
0
    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
Example #3
0
 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
Example #4
0
    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)
Example #5
0
    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
Example #6
0
    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
Example #7
0
    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
Example #8
0
    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
Example #9
0
    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
Example #10
0
    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