Ejemplo n.º 1
0
    async def leave(self, ctx, *arg):
        """
        `!leave` __`Removes an existing role from yourself`__

        **Usage:** !leave [role name]

        **Examples:**
        `!leave L2A` removes the L2A role from yourself
        """

        # case where role name is space separated
        name = " ".join(arg).lower()

        if not name:
            raise BadArgs("", show_help=True)

        aliases = {"he": "he/him/his", "she": "she/her/hers", "ze": "ze/zir/zirs", "they": "they/them/theirs"}

        # Convert alias to proper name
        if name.lower() in aliases:
            name = aliases[name]

        role = next((r for r in ctx.guild.roles if name == r.name.lower()), None)

        if not role:
            raise BadArgs("that role doesn't exist!")

        if role not in ctx.author.roles:
            raise BadArgs("you don't have that role!")

        await ctx.author.remove_roles(role)
        await ctx.send("role removed!", delete_after=5)
Ejemplo n.º 2
0
    async def leave(self, ctx: commands.Context, *arg: str):
        """
        `!leave` __`Removes an existing role from yourself`__

        **Usage:** !leave [role name]

        **Examples:**
        `!leave Study Group` removes the Study Group role from yourself
        """

        # case where role name is space separated
        name = " ".join(arg).lower()

        if not name:
            raise BadArgs("", show_help=True)

        try:
            role = await self.role_converter.convert(ctx, name)
        except commands.RoleNotFound:
            raise BadArgs("That role doesn't exist!", show_help=True)

        if role not in ctx.author.roles:
            raise BadArgs("you don't have that role!")

        await ctx.author.remove_roles(role)
        await ctx.send("role removed!", delete_after=5)
Ejemplo n.º 3
0
    async def ensure_voice_state(self, ctx: commands.Context):
        if not ctx.author.voice or not ctx.author.voice.channel:
            raise BadArgs("You are not connected to any voice channel.")

        if ctx.voice_client:
            if ctx.voice_client.channel != ctx.author.voice.channel:
                raise BadArgs("Bot is already in a voice channel.")
Ejemplo n.º 4
0
    async def dm(self, ctx):
        """
        `!dm` __`213DM Generator`__

        **Usage:** !dm <user | close> [user] [...]

        **Examples:**
        `!dm @blankuser#1234` creates 213DM with TAs and blankuser
        `!dm @blankuser#1234 @otheruser#5678` creates 213DM with TAs, blankuser and otheruser
        `!dm close` closes 213DM
        """

        # meant for 213 server
        guild = self.bot.get_guild(796222302483251241)

        if "close" in ctx.message.content.lower():
            if not ctx.channel.name.startswith("213dm-"):
                raise BadArgs("This is not a 213DM.")

            await ctx.send("Closing 213DM.")
            await next(i for i in guild.roles if i.name == ctx.channel.name).delete()
            return await ctx.channel.delete()

        if not ctx.message.mentions:
            raise BadArgs("You need to specify a user or users to add!", show_help=True)

        # check that nobody is already in a 213dm before going and creating everything
        for user in ctx.message.mentions:
            for role in user.roles:
                if role.name.startswith("213dm"):
                    raise BadArgs(f"{user.name} is already in a 213DM.")

        # generate customized channel name to allow customized role
        nam = int(str((datetime.now() - datetime(1970, 1, 1)).total_seconds()).replace(".", "")) + ctx.author.id
        nam = f"213dm-{nam}"
        # create custom role
        role = await guild.create_role(name=nam, colour=discord.Colour(0x88ff88))

        for user in ctx.message.mentions:
            try:
                await user.add_roles(role)
            except (discord.Forbidden, discord.HTTPException):
                pass  # if for whatever reason one of the people doesn't exist, just ignore and keep going

        access = discord.PermissionOverwrite(read_messages=True, send_messages=True, read_message_history=True)
        noaccess = discord.PermissionOverwrite(read_messages=False, read_message_history=False, send_messages=False)
        overwrites = {
            # allow Computers and the new role, deny everyone else
            guild.default_role                : noaccess,
            guild.get_role(796222302697816121): access,
            role                              : access
        }
        # this id is id of group dm category
        channel = await guild.create_text_channel(nam, overwrites=overwrites, category=guild.get_channel(796505656437768233))
        await ctx.send("Opened channel.")
        users = (f"<@{usr.id}>" for usr in ctx.message.mentions)
        await channel.send(f"<@{ctx.author.id}> {' '.join(users)}\n" +
                           f"Welcome to 213 private DM. Type `!dm close` to exit when you are finished.")
Ejemplo n.º 5
0
    async def userstats(self, ctx, *userid):
        """
        `!userstats` __`Check user profile and stats`__

        **Usage:** !userstats <USER ID>

        **Examples:** `!userstats 375445489627299851` [embed]
        """

        if not userid:
            user = ctx.author
        else:
            try:
                userid = int(userid[0])
            except ValueError:
                raise BadArgs("Please enter a user id", show_help=True)

            user = ctx.guild.get_member(userid)

        if not user:
            raise BadArgs("That user does not exist")

        # we use both user and member objects, since some stats can only be obtained
        # from either user or member object

        async with ctx.channel.typing():
            most_active_channel = 0
            most_active_channel_name = None
            cum_message_count = 0
            yesterday = (datetime.now() - timedelta(days=1)).replace(tzinfo=pytz.timezone("US/Pacific")).astimezone(timezone.utc).replace(tzinfo=None)

            for channel in ctx.guild.text_channels:
                counter = 0

                async for message in channel.history(after=yesterday, limit=None):
                    if message.author == user:
                        counter += 1
                        cum_message_count += 1

                if counter > most_active_channel:
                    most_active_channel = counter
                    most_active_channel_name = "#" + channel.name

            embed = discord.Embed(title=f"Report for user `{user.name}#{user.discriminator}` (all times in UTC)")
            embed.add_field(name="Date Joined", value=user.joined_at.strftime("%A, %Y %B %d @ %H:%M:%S"), inline=True)
            embed.add_field(name="Account Created", value=user.created_at.strftime("%A, %Y %B %d @ %H:%M:%S"), inline=True)
            embed.add_field(name="Roles", value=", ".join([str(i) for i in sorted(user.roles[1:], key=lambda role: role.position, reverse=True)]), inline=True)
            embed.add_field(name="Most active text channel in last 24 h", value=f"{most_active_channel_name} ({most_active_channel} messages)", inline=True)
            embed.add_field(name="Total messages sent in last 24 h", value=str(cum_message_count), inline=True)

            await ctx.send(embed=embed)
Ejemplo n.º 6
0
    async def join(self, ctx, *arg):
        """
        `!join` __`Adds a role to yourself`__

        **Usage:** !join [role name]

        **Examples:**
        `!join L2A` adds the L2A role to yourself

        **Valid Roles:**
        L2A, L2B, L2C, L2D, L2E, L2F, L2G, L2H, L2J, L2K, L2L, L2M, L2N, L2P, L2Q, L2R, L2S, He/Him/His, She/Her/Hers, They/Them/Theirs, Ze/Zir/Zirs
        """

        # case where role name is space separated
        name = " ".join(arg).lower()

        # Display help if given no argument
        if not name:
            raise BadArgs("", show_help=True)

        # make sure that you can't add roles like "prof" or "ta"
        valid_roles = ["L2A", "L2B", "L2C", "L2D", "L2E", "L2F", "L2G", "L2H", "L2J", "L2K", "L2L", "L2M", "L2N", "L2P", "L2Q", "L2R", "L2S", "He/Him/His", "She/Her/Hers", "They/Them/Theirs", "Ze/Zir/Zirs"]
        aliases = {"he": "He/Him/His", "she": "She/Her/Hers", "ze": "Ze/Zir/Zirs", "they": "They/Them/Theirs"}

        # Convert alias to proper name
        if name.lower() in aliases:
            name = aliases[name].lower()

        # Ensure that people only add one lab role
        if name.startswith("l2") and any(role.name.startswith("L2") for role in ctx.author.roles):
            raise BadArgs("You already have a lab role!")

        # Grab the role that the user selected
        role = next((r for r in ctx.guild.roles if name == r.name.lower()), None)

        # Check that the role actually exists
        if not role:
            raise BadArgs("You can't add that role!", show_help=True)

        # Ensure that the author does not already have the role
        if role in ctx.author.roles:
            raise BadArgs("you already have that role!")

        # Special handling for roles that exist but can not be selected by a student
        if role.name not in valid_roles:
            self.add_instructor_role_counter += 1

            if self.add_instructor_role_counter > 5:
                if self.add_instructor_role_counter == 42:
                    if random.random() > 0.999:
                        raise BadArgs("Congratulations, you found the secret message. IDEK how you did it, but good job. Still can't add the instructor role though. Bummer, I know.")
                elif self.add_instructor_role_counter == 69:
                    if random.random() > 0.9999:
                        raise BadArgs("nice.")
                raise BadArgs("You can't add that role, but if you try again, maybe something different will happen on the 42nd attempt")
            else:
                raise BadArgs("you cannot add an instructor/invalid role!", show_help=True)

        await ctx.author.add_roles(role)
        await ctx.send("role added!", delete_after=5)
Ejemplo n.º 7
0
    async def skip(self, ctx: commands.Context):
        """
        `!skip` __`Skip song`__
        **Aliases:** s

        **Usage:** !skip

        **Examples:**
        `!skip` skips current song

        **Note:** requires at least 3 votes.
        """

        if not self.voice_state.is_playing:
            raise BadArgs("Not playing any music right now...")

        voter = ctx.message.author

        if voter == self.voice_state.current.requester:
            await ctx.message.add_reaction("⏭")
            self.voice_state.skip()
        elif voter.id not in self.voice_state.skip_votes:
            self.voice_state.skip_votes.add(voter.id)
            total_votes = len(self.voice_state.skip_votes)

            if total_votes >= 3 or voter.id in self.bot.owner_ids:
                await ctx.message.add_reaction("⏭")
                self.voice_state.skip()
            else:
                await ctx.send(
                    f"Skip vote added, currently at **{total_votes}/3**")
        else:
            await ctx.send("You have already voted to skip this song.")
Ejemplo n.º 8
0
    async def queue(self, ctx: commands.Context, *, page: int = 1):
        """
        `!queue` __`Song queue`__
        **Aliases:** q

        **Usage:** !queue

        **Examples:**
        `!queue` shows current queue
        """

        if not self.voice_state.songs:
            raise BadArgs("Empty queue.")

        items_per_page = 10
        pages = math.ceil(len(self.voice_state.songs) / items_per_page)

        start = (page - 1) * items_per_page
        end = start + items_per_page

        queue = ""

        for i, song in enumerate(self.voice_state.songs[start:end],
                                 start=start):
            queue += f"`{i + 1}.` [**{song.source.title}**]({song.source.url})\n"

        embed = discord.Embed(
            description=f"**{len(self.voice_state.songs)} tracks:**\n\n{queue}",
            colour=random.randint(0, 0xFFFFFF))
        embed.set_footer(text=f"Viewing page {page}/{pages}")
        await ctx.send(embed=embed)
Ejemplo n.º 9
0
    async def unlive(self, ctx: commands.Context):
        """
        `!unlive`

        Disables course tracking for the channel the command is invoked in.
        """

        c_handler = self._get_canvas_handler(ctx.message.guild)

        if not isinstance(c_handler, CanvasHandler):
            raise BadArgs("Canvas Handler doesn't exist.")

        if ctx.message.channel in c_handler.live_channels:
            c_handler.live_channels.remove(ctx.message.channel)

            self.canvas_dict[str(ctx.message.guild.id)]["live_channels"] = [
                channel.id for channel in c_handler.live_channels
            ]
            writeJSON(self.canvas_dict, "data/canvas.json")

            for course in c_handler.courses:
                watchers_file = f"{util.canvas_handler.COURSES_DIRECTORY}/{course.id}/watchers.txt"
                CanvasHandler.delete_channels_from_file([ctx.message.channel],
                                                        watchers_file)

                # If there are no more channels watching the course, we should delete that course's directory.
                if os.stat(watchers_file).st_size == 0:
                    shutil.rmtree(
                        f"{util.canvas_handler.COURSES_DIRECTORY}/{course.id}")

            await ctx.send("Removed channel from live tracking.")
        else:
            await ctx.send("Channel was not live tracking.")
Ejemplo n.º 10
0
    async def ppinned(self, ctx):
        """
        `!ppinned`

        **Usage:** !ppinned

        **Examples:**
        `!ppinned` sends a list of the Piazza's pinned posts to the calling channel

        *to prevent hitting the rate-limit, only usable once every 5 secs channel-wide*
        """

        if self.bot.d_handler.piazza_handler:
            posts = self.bot.d_handler.piazza_handler.get_pinned()
            embed = discord.Embed(
                title=
                f"**Pinned posts for {self.bot.d_handler.piazza_handler.course_name}:**",
                colour=0x497aaa)

            for post in posts:
                embed.add_field(name=f"@{post['num']}",
                                value=f"[{post['subject']}]({post['url']})",
                                inline=False)

            embed.set_footer(text=f"Requested by {ctx.author.display_name}",
                             icon_url=str(ctx.author.avatar_url))

            await ctx.send(embed=embed)
        else:
            raise BadArgs("Piazza hasn't been instantiated yet!")
Ejemplo n.º 11
0
    async def help(self, ctx, *arg):
        """
        `!help` __`Returns list of commands or usage of command`__

        **Usage:** !help [optional cmd]

        **Examples:**
        `!help` [embed]
        """

        if not arg:
            embed = discord.Embed(title="CS213 Bot", description="Commands:", colour=random.randint(0, 0xFFFFFF), timestamp=datetime.utcnow())
            embed.add_field(name=f"❗ Current Prefix: `{self.bot.command_prefix}`", value="\u200b", inline=False)

            for k, v in self.bot.cogs.items():
                embed.add_field(name=k, value=" ".join(f"`{i}`" for i in v.get_commands() if not i.hidden), inline=False)

            embed.set_thumbnail(url=self.bot.user.avatar_url)
            embed.add_field(name = "_ _\nSupport Bot Development: visit the CS213Bot repo at https://github.com/jbrightuniverse/cs213bot/", value = "_ _\nCS213Bot is based on CS221Bot. Support them at https://github.com/Person314159/cs221bot/\n\nCall ++help to access C++Bot from within this bot.\nhttps://github.com/jbrightuniverse/C-Bot")
            embed.set_footer(text=f"The sm213 language was created by Dr. Mike Feeley of the CPSC department at UBCV.\nUsed with permission.\n\nRequested by {ctx.author.display_name}", icon_url=str(ctx.author.avatar_url))
            await ctx.send(embed=embed)
        else:
            help_command = arg[0]

            comm = self.bot.get_command(help_command)

            if not comm or not comm.help or comm.hidden:
                raise BadArgs("That command doesn't exist.")

            await ctx.send(comm.help)
Ejemplo n.º 12
0
    async def join(self, ctx, *arg):
        """
        `!join` __`Adds a role to yourself`__

        **Usage:** !join [role name]

        **Examples:**
        `!join notify` adds the notify role to yourself

        **Valid Roles:**
        notify (`!join notify`), He/Him/His (`!join he`), She/Her/Hers (`!join she`), They/Them/Theirs (`!join they`), Ze/Zir/Zirs (`!join ze`)
        """

        # case where role name is space separated
        name = " ".join(arg).lower()

        # Display help if given no argument
        if not name:
            raise BadArgs("", show_help=True)

        # make sure that you can't add roles like "prof" or "ta"
        valid_roles = ["L1A", "L1B", "L1C", "L1D", "L1E", "L1F", "L1G", "notify", "He/Him/His", "She/Her/Hers", "They/Them/Theirs", "Ze/Zir/Zirs"]
        aliases = {"he": "He/Him/His", "she": "She/Her/Hers", "ze": "Ze/Zir/Zirs", "they": "They/Them/Theirs"}

        # Convert alias to proper name
        if name.lower() in aliases:
            name = aliases[name].lower()

        # Grab the role that the user selected
        role = next((r for r in ctx.guild.roles if name == r.name.lower()), None)

        # Check that the role actually exists
        if not role:
            raise BadArgs("You can't add that role!", show_help=True)

        # Ensure that the author does not already have the role
        if role in ctx.author.roles:
            raise BadArgs("you already have that role!")

        # Special handling for roles that exist but can not be selected by a student
        if role.name not in valid_roles:    
            raise BadArgs("you cannot add an instructor/invalid role!", show_help=True)

        await ctx.author.add_roles(role)
        await ctx.send("role added!", delete_after=5)
Ejemplo n.º 13
0
    async def asgn(self, ctx: commands.Context, *args):
        """
        `!asgn ( | (-due (n-(hour|day|week|month|year)) | YYYY-MM-DD | YYYY-MM-DD-HH:MM:SS) | -all)`

        Argument can be left blank for sending assignments due 2 weeks from now.

        *Filter till due date:*

        `!asgn -due` can be in time from now e.g.: `-due 4-hour` or all assignments before a certain date e.g.: `-due 2020-10-21`

        *All assignments:*

        `!asgn -all` returns ALL assignments.
        """

        c_handler = self._get_canvas_handler(ctx.message.guild)

        if not isinstance(c_handler, CanvasHandler):
            raise BadArgs("Canvas Handler doesn't exist.")

        if args and args[0].startswith("-due"):
            due = args[1]
            course_ids = args[2:]
        elif args and args[0].startswith("-all"):
            due = None
            course_ids = args[1:]
        else:
            due = "2-week"
            course_ids = args

        assignments = c_handler.get_assignments(due, course_ids,
                                                CANVAS_API_URL)

        if not assignments:
            pattern = r"\d{4}-\d{2}-\d{2}"
            return await ctx.send(
                f"No assignments due by {due}{' (at 00:00)' if re.match(pattern, due) else ''}."
            )

        for data in assignments:
            embed_var = discord.Embed(title=data[2],
                                      url=data[3],
                                      description=data[4],
                                      color=CANVAS_COLOR,
                                      timestamp=datetime.strptime(
                                          data[5], "%Y-%m-%d %H:%M:%S"))
            embed_var.set_author(name=data[0], url=data[1])
            embed_var.set_thumbnail(url=CANVAS_THUMBNAIL_URL)
            embed_var.add_field(name="Due at", value=data[6], inline=True)
            embed_var.set_footer(text="Created at",
                                 icon_url=CANVAS_THUMBNAIL_URL)
            await ctx.send(embed=embed_var)
Ejemplo n.º 14
0
    async def pread(self, ctx, postID):
        """
        `!pread` __`post id`__

        **Usage:** !pread <post id>

        **Examples:**
        `!pread 828` returns an embed with the [post](https://piazza.com/class/ke1ukp9g4xx6oi?cid=828)'s
        info (question, answer, answer type, tags)
        """

        if not self.bot.d_handler.piazza_handler:
            raise BadArgs("Piazza hasn't been instantiated yet!")

        try:
            post = self.bot.d_handler.piazza_handler.get_post(postID)
        except InvalidPostID:
            raise BadArgs("Post not found.")

        if post:
            post_embed = self.create_post_embed(post)
            await ctx.send(embed=post_embed)
Ejemplo n.º 15
0
    async def joinrole(self, ctx: commands.Context, *arg: str):
        """
        `!joinrole` __`Adds a role to yourself`__

        **Usage:** !joinrole [role name]

        **Examples:**
        `!joinrole Study Group` adds the Study Group role to yourself

        **Valid Roles:**
        Looking for Partners, Study Group, He/Him/His, She/Her/Hers, They/Them/Theirs, Ze/Zir/Zirs, notify
        """

        await ctx.message.delete()

        # case where role name is space separated
        name = " ".join(arg)

        # Display help if given no argument
        if not name:
            raise BadArgs("", show_help=True)

        # make sure that you can't add roles like "prof" or "ta"
        valid_roles = ["Looking for Partners", "Study Group", "He/Him/His", "She/Her/Hers", "They/Them/Theirs", "Ze/Zir/Zirs", "notify"]

        # Grab the role that the user selected
        # Converters! this also makes aliases unnecessary
        try:
            role = await self.role_converter.convert(ctx, name)
        except commands.RoleNotFound:
            raise BadArgs("You can't add that role!", show_help=True)

        # Ensure that the author does not already have the role
        if role in ctx.author.roles:
            raise BadArgs("you already have that role!")

        # Special handling for roles that exist but can not be selected by a student
        if role.name not in valid_roles:
            self.add_instructor_role_counter += 1

            if self.add_instructor_role_counter > 5:
                if self.add_instructor_role_counter == 42:
                    if random.random() > 0.999:
                        raise BadArgs("Congratulations, you found the secret message. IDEK how you did it, but good job. Still can't add the instructor role though. Bummer, I know.")
                elif self.add_instructor_role_counter == 69:
                    if random.random() > 0.9999:
                        raise BadArgs("nice.")
                raise BadArgs("You can't add that role, but if you try again, maybe something different will happen on the 42nd attempt")
            else:
                raise BadArgs("you cannot add an instructor/invalid role!", show_help=True)

        await ctx.author.add_roles(role)
        await ctx.send("role added!", delete_after=5)
Ejemplo n.º 16
0
    async def shuffle(self, ctx: commands.Context):
        """
        `!shuffle` __`Shuffle queue`__

        **Usage:** !shuffle

        **Examples:**
        `!shuffle` shuffles queue
        """

        if not self.voice_state.songs:
            raise BadArgs("Empty queue.")

        self.voice_state.songs.shuffle()
        await ctx.message.add_reaction("✅")
Ejemplo n.º 17
0
    async def remove(self, ctx: commands.Context, index: int):
        """
        `!remove` __`Remove song`__
        **Aliases:** r

        **Usage:** !remove <index>

        **Examples:**
        `!remove 2` removes second song
        """

        if not self.voice_state.songs:
            raise BadArgs("Empty queue.")

        self.voice_state.songs.remove(index - 1)
        await ctx.message.add_reaction("✅")
Ejemplo n.º 18
0
    async def track(self, ctx: commands.Context, *course_ids: str):
        """
        `!track <course IDs...>`

        Add the courses with given IDs to the list of courses being tracked. Note that you will
        only receive course updates in channels that you have typed `!live` in.
        """

        self._add_guild(ctx.message.guild)

        c_handler = self._get_canvas_handler(ctx.message.guild)

        if not isinstance(c_handler, CanvasHandler):
            raise BadArgs("Canvas Handler doesn't exist.")

        c_handler.track_course(course_ids, self.bot.notify_unpublished)

        await self.send_canvas_track_msg(c_handler, ctx)
Ejemplo n.º 19
0
    async def untrack(self, ctx: commands.Context, *course_ids: str):
        """
        `!untrack <course IDs...>`

        Remove the courses with given IDs from the list of courses being tracked.
        """

        c_handler = self._get_canvas_handler(ctx.message.guild)

        if not isinstance(c_handler, CanvasHandler):
            raise BadArgs("Canvas Handler doesn't exist.")

        c_handler.untrack_course(course_ids)

        if not c_handler.courses:
            self.bot.d_handler.canvas_handlers.remove(c_handler)

        await self.send_canvas_track_msg(c_handler, ctx)
Ejemplo n.º 20
0
    async def annc(self, ctx: commands.Context, *args):
        """
        `!annc ( | (-since (n-(hour|day|week|month|year)) | YYYY-MM-DD | YYYY-MM-DD-HH:MM:SS) | -all)`

        Argument can be left blank for sending announcements from 2 weeks ago to now.

        *Filter since announcement date:*

        `!annc -since` can be in time from now e.g.: `-since 4-hour` or all announcements after a certain date e.g.: `-since 2020-10-21`

        *All announcements:*

        `!annc -all` returns ALL announcements.
        """

        c_handler = self._get_canvas_handler(ctx.message.guild)

        if not isinstance(c_handler, CanvasHandler):
            raise BadArgs("Canvas Handler doesn't exist.")

        if args and args[0].startswith("-since"):
            since = args[1]
            course_ids = args[2:]
        elif args and args[0].startswith("-all"):
            since = None
            course_ids = args[1:]
        else:
            since = "2-week"
            course_ids = args

        for data in c_handler.get_course_stream_ch(since, course_ids,
                                                   CANVAS_API_URL,
                                                   CANVAS_API_KEY):
            embed_var = discord.Embed(title=data[2],
                                      url=data[3],
                                      description=data[4],
                                      color=CANVAS_COLOR)
            embed_var.set_author(name=data[0], url=data[1])
            embed_var.set_thumbnail(url=CANVAS_THUMBNAIL_URL)
            embed_var.add_field(name="Created at", value=data[5], inline=True)
            await ctx.send(embed=embed_var)
Ejemplo n.º 21
0
    async def gtcycle(self, ctx, limit, *, txt):
        """
        `!gtcycle` __`Google Translate Cycler`__

        **Usage:** !gtcycle <number of languages | all> <text>

        **Examples:**
        `!gtcycle all hello!` cycles through all languages with input text "hello!"
        `!gtcycle 12 hello!` cycles through 12 languages with input text "hello!"
        """

        lang_list = list(constants.LANGUAGES)
        random.shuffle(lang_list)

        if limit == "all":
            limit = len(lang_list)
        elif not (limit.isdecimal() and 1 <
                  (limit := int(limit)) < len(constants.LANGUAGES)):
            raise BadArgs(
                f"Please send a positive integer number of languages less than {len(constants.LANGUAGES)} to cycle."
            )
Ejemplo n.º 22
0
    async def votes(self, ctx: commands.Context):
        """
        `!votes` __`Top votes for server icon`__

        **Usage:** !votes

        **Examples:**
        `!votes` returns top 5 icons sorted by score
        """

        async with ctx.channel.typing():
            images = []

            for c in ctx.guild.text_channels:
                if c.name == "course-events":
                    channel = c
                    break
            else:
                raise BadArgs("votes channel doesn't exist.")

            async for message in channel.history():
                if message.attachments or message.embeds:
                    count = 0

                    for reaction in message.reactions:
                        if reaction.emoji == "⬆️":
                            count += reaction.count - (
                                message.author in await
                                reaction.users().flatten())

                    images.append([message.attachments[0].url, count])

            images = sorted(images, key=lambda image: image[1],
                            reverse=True)[:5]

            for image in images:
                embed = discord.Embed(colour=random.randint(0, 0xFFFFFF))
                embed.add_field(name="Score", value=image[1], inline=True)
                embed.set_thumbnail(url=image[0])
                await ctx.send(embed=embed)
Ejemplo n.º 23
0
    async def help(self, ctx: commands.Context, *arg: str):
        """
        `!help` __`Returns list of commands or usage of command`__

        **Usage:** !help [optional cmd]

        **Examples:**
        `!help` [embed]
        """

        if not arg:
            embed = discord.Embed(title="CS221 Bot",
                                  description="Commands:",
                                  colour=random.randint(0, 0xFFFFFF),
                                  timestamp=datetime.utcnow())
            embed.add_field(
                name=f"❗ Current Prefix: `{self.bot.command_prefix}`",
                value="\u200b",
                inline=False)

            for k, v in sorted(self.bot.cogs.items(), key=lambda kv: kv[0]):
                embed.add_field(name=k,
                                value=" ".join(f"`{i}`"
                                               for i in v.get_commands()
                                               if not i.hidden),
                                inline=False)

            embed.set_thumbnail(url=self.bot.user.avatar_url)
            embed.set_footer(text=f"Requested by {ctx.author.display_name}",
                             icon_url=str(ctx.author.avatar_url))
            await ctx.send(embed=embed)
        else:
            help_command = arg[0]

            comm = self.bot.get_command(help_command)

            if not comm or not comm.help or comm.hidden:
                raise BadArgs("That command doesn't exist.")

            await ctx.send(comm.help)
Ejemplo n.º 24
0
    async def play(self, ctx: commands.Context, *, search: str):
        """
        `!play` __`Play song`__
        **Aliases:** p

        **Usage:** !play <url>

        **Examples:**
        `!play https://www.youtube.com/watch?v=dQw4w9WgXcQ` plays Never Gonna Give You Up
        """

        if not re.match(r"https://(www\.youtube|soundcloud)\.com",
                        search,
                        flags=re.IGNORECASE):
            raise BadArgs("Only links allowed.")

        if not ctx.voice_client:
            destination = ctx.author.voice.channel

            if self.voice_state.voice:
                await self.voice_state.voice.move_to(destination)
                return

            self.voice_state.voice = await destination.connect()

        async with ctx.typing():
            try:
                source = await YTDLSource.create_source(ctx,
                                                        search,
                                                        loop=self.bot.loop)
            except YTDLError as e:
                await ctx.send(
                    f"An error occurred while processing this request: {e}",
                    delete_after=5)
            else:
                song = Song(source)

                await self.voice_state.songs.put(song)
                await ctx.send(f"Enqueued {source}")
Ejemplo n.º 25
0
    async def live(self, ctx: commands.Context):
        """
        `!live`

        Enables course tracking for the channel the command is invoked in.
        """

        c_handler = self._get_canvas_handler(ctx.message.guild)

        if not isinstance(c_handler, CanvasHandler):
            raise BadArgs("Canvas Handler doesn't exist.")

        if ctx.message.channel not in c_handler.live_channels:
            c_handler.live_channels.append(ctx.message.channel)

            for course in c_handler.courses:
                modules_file = f"{util.canvas_handler.COURSES_DIRECTORY}/{course.id}/modules.txt"
                watchers_file = f"{util.canvas_handler.COURSES_DIRECTORY}/{course.id}/watchers.txt"
                CanvasHandler.store_channels_in_file([ctx.message.channel],
                                                     watchers_file)

                create_file_if_not_exists(modules_file)

                # Here, we will only download modules if modules_file is empty.
                if os.stat(modules_file).st_size == 0:
                    CanvasHandler.download_modules(course,
                                                   self.bot.notify_unpublished)

            self.canvas_dict[str(ctx.message.guild.id)]["live_channels"] = [
                channel.id for channel in c_handler.live_channels
            ]
            writeJSON(self.canvas_dict, "data/canvas.json")

            await ctx.send("Added channel to live tracking.")
        else:
            await ctx.send("Channel already live tracking.")
Ejemplo n.º 26
0
    async def bst(self, ctx):
        """
        `!bst` __`Binary Search Tree analysis tool`__
        **Usage:** !bst <node> [node] [...]
        **Examples:**
        `!bst 2 1 3` displays a BST in ASCII and PNG form with root node 2 and leaves 1, 3
        `!bst 4 5 6` displays a BST in ASCII and PNG form with root node 4, parent node 5 and leaf 6

        Launching the command activates a 60 second window during which additional unprefixed commands can be called:

        `pre`        displays pre-order traversal of the tree
        `in`         displays in-order traversal of the tree
        `level`      displays level-order traversal of the tree
        `post`       displays post-order traversal of the tree
        `about`      displays characteristics of the tree
        `pause`      stops the 60 second countdown timer
        `unpause`    starts the 60 second countdown timer
        `show`       displays the ASCII and PNG representations of the tree again
        `exit`       exits the window

        `insert <node> [node] [...]` inserts nodes into the tree
        **Example:** `insert 5 7 6`  inserts nodes 5, 7 and 6, in that order

        `delete <node> [node] [...]` deletes nodes from the tree
        **Example:** `delete 7 8 9`  deletes nodes 7, 8 and 9, in that order
        """

        numbers = []

        for num in ctx.message.content[5:].replace(",", "").split():
            if re.fullmatch(r"[+-]?((\d+(\.\d*)?)|(\.\d+))", num):
                try:
                    numbers.append(int(num))
                except ValueError:
                    numbers.append(float(num))
            else:
                raise BadArgs("Please provide valid numbers for the tree.")

        if not numbers:
            raise BadArgs("Please provide some numbers for the tree.",
                          show_help=True)

        root = Node(numbers[0])

        nodes = [root]
        highlighted = []

        def insert(subroot, num, highlight=False):
            if highlight:
                highlighted.append(subroot.val)

            if num < subroot.val:
                if not subroot.left:
                    node = Node(num)
                    subroot.left = node

                    if highlight:
                        highlighted.append(num)

                    nodes.append(node)
                else:
                    insert(subroot.left, num, highlight)
            else:
                if not subroot.right:
                    node = Node(num)
                    subroot.right = node

                    if highlight:
                        highlighted.append(num)

                    nodes.append(node)
                else:
                    insert(subroot.right, num, highlight)

        def delete(subroot, num):
            if subroot:
                if subroot.val == num:
                    if subroot.left is not None and subroot.right is not None:
                        parent = subroot
                        predecessor = subroot.left

                        while predecessor.right is not None:
                            parent = predecessor
                            predecessor = predecessor.right

                        if parent.right == predecessor:
                            parent.right = predecessor.left
                        else:
                            parent.left = predecessor.left

                        predecessor.left = subroot.left
                        predecessor.right = subroot.right

                        ret = predecessor
                    else:
                        if subroot.left is not None:
                            ret = subroot.left
                        else:
                            ret = subroot.right

                    nodes.remove(subroot)
                    del subroot
                    return ret
                else:
                    if subroot.val > num:
                        if subroot.left:
                            subroot.left = delete(subroot.left, num)
                    else:
                        if subroot.right:
                            subroot.right = delete(subroot.right, num)

            return subroot

        def get_node(num):
            for node in nodes:
                if node.val == num:
                    return node

            return None

        for num in numbers[1:]:
            if not get_node(num):
                insert(root, num)

        timeout = 60
        display = True
        filex = None

        async def draw_bst(highlighted, root):
            entries = list(
                filter(lambda x: x[0] is not None, [[
                    b, i
                ] for b, i in zip(root.values, range(1,
                                                     len(root.values) + 1))]))
            levels = root.height + 1
            fsize = max(10, 60 - 7 * root.height)
            font = ImageFont.truetype("boxfont_round.ttf", fsize)
            radius = max(10, 100 - 10 * root.height)
            width = 2 * radius * (2**(root.height + 1))
            height = 2 * radius * (root.height + 2)
            basey = height // (levels + 1)
            smallest = [math.inf, 1]

            for entry in entries:
                if entry[0] < smallest[0]:
                    smallest = entry

            reflevel = math.floor(math.log(smallest[1], 2))
            basex = width // (2**reflevel + 1)
            refx = int(basex - 2 * radius)

            if basex != width // 2:
                refx = 0
                offset = 0
            else:
                offset = radius

            image = Image.new("RGBA", (width - refx - offset, height),
                              (255, 255, 255, 255))
            layer = Image.new("RGBA", (width - refx - offset, height),
                              (0, 0, 0, 0))
            drawing = ImageDraw.Draw(image)
            drawing2 = ImageDraw.Draw(layer)
            currentlevel = 2
            x = width // 2
            y = basey * currentlevel

            if root.val in highlighted:
                col = (0, 128, 128, 255)
            else:
                col = (0, 0, 255, 255)

            drawing2.ellipse([(x - radius - refx, basey - radius),
                              (x + radius - refx, basey + radius)],
                             fill=col,
                             outline=(0, 0, 0, 255))
            ln = drawing2.textsize(str(root.val), font=font)[0] / 2
            drawing2.text((x - refx - fsize // 2 - ln, basey - fsize // 2),
                          str(root.val),
                          fill=(255, 168, 0, 255),
                          font=font)

            for entry in entries[1:]:
                await asyncio.sleep(0)

                if math.floor(math.log(entry[1], 2)) > currentlevel - 1:
                    currentlevel += 1
                    y = basey * currentlevel

                multiplier = entry[1] - 2**(currentlevel - 1) + 1
                basex = width // (2**(currentlevel - 1) + 1)
                x = basex * multiplier
                prevx = width // (2**(currentlevel - 2) + 1)
                prevx *= (multiplier + 1) // 2

                if entry[0] in highlighted:
                    col = (0, 128, 128, 255)
                    linecol = col
                else:
                    col = (0, 0, 255, 255)
                    linecol = (0, 0, 0, 255)

                drawing.line([(prevx - refx, basey * (currentlevel - 1)),
                              (x - refx, y)],
                             fill=linecol,
                             width=7)
                drawing2.ellipse([(x - radius - refx, y - radius),
                                  (x + radius - refx, y + radius)],
                                 fill=col,
                                 outline=(0, 0, 0, 255))
                ln = drawing2.textsize(str(entry[0]), font=font)[0] / 2
                drawing2.text((x - refx - fsize // 2 - ln, y - fsize // 2),
                              str(entry[0]),
                              fill=(255, 168, 0, 255),
                              font=font)

            image.alpha_composite(layer)
            filex = BytesIO()
            image.save(filex, "PNG", optimize=True)
            filex.seek(0)
            return filex

        while True:
            if root.height <= 8:
                filex = await draw_bst(highlighted, root)
                highlighted = []

            if display:
                text = f"```{root}\n```"
                await ctx.send(
                    text,
                    file=discord.File(filex, "bst.png") if filex else None)
                display = False

            def check(m):
                return m.channel.id == ctx.channel.id and m.author.id == ctx.author.id

            try:
                message = await self.bot.wait_for("message",
                                                  timeout=timeout,
                                                  check=check)
            except asyncio.exceptions.TimeoutError:
                return

            command = message.content.replace(",", "").replace("!", "").lower()
            timeout = 60

            if command.startswith("level"):
                await ctx.send("Level-Order Traversal:\n**" +
                               "  ".join([str(n.val)
                                          for n in root.levelorder]) + "**")
            elif command.startswith("pre"):
                await ctx.send("Pre-Order Traversal:\n**" +
                               "  ".join([str(n.val)
                                          for n in root.preorder]) + "**")
            elif command.startswith("post"):
                await ctx.send("Post-Order Traversal:\n**" +
                               "  ".join([str(n.val)
                                          for n in root.postorder]) + "**")
            elif command.startswith("in") and not command.startswith("ins"):
                await ctx.send("In-Order Traversal:\n**" +
                               "  ".join([str(n.val)
                                          for n in root.inorder]) + "**")
            elif command.startswith("about"):
                embed = discord.Embed(title="Binary Search Tree Info",
                                      description="> " +
                                      text.replace("\n", "\n> "),
                                      color=random.randint(0, 0xffffff))
                embed.add_field(name="Height:", value=str(root.height))
                embed.add_field(name="Balanced?", value=str(root.is_balanced))
                embed.add_field(name="Complete?", value=str(root.is_complete))
                embed.add_field(name="Full?", value=str(root.is_strict))
                embed.add_field(name="Perfect?", value=str(root.is_perfect))
                embed.add_field(name="Number of leaves:",
                                value=str(root.leaf_count))
                embed.add_field(name="Max Leaf Depth:",
                                value=str(root.max_leaf_depth))
                embed.add_field(name="Min Leaf Depth:",
                                value=str(root.min_leaf_depth))
                embed.add_field(name="Max Node Value:",
                                value=str(root.max_node_value))
                embed.add_field(name="Min Node Value:",
                                value=str(root.min_node_value))
                embed.add_field(name="Entries:", value=str(root.size))
                embed.add_field(name="Pre-Order Traversal:",
                                value=" ".join(
                                    [str(n.val) for n in root.preorder]))
                embed.add_field(name="In-Order Traversal:",
                                value=" ".join(
                                    [str(n.val) for n in root.inorder]))
                embed.add_field(name="Level-Order Traversal:",
                                value=" ".join(
                                    [str(n.val) for n in root.levelorder]))
                embed.add_field(name="Post-Order Traversal:",
                                value=" ".join(
                                    [str(n.val) for n in root.postorder]))

                if root.left:
                    embed.add_field(name="In-Order Predecessor:",
                                    value=max(
                                        filter(lambda x: x is not None,
                                               root.left.values)))

                if root.right:
                    embed.add_field(name="In-Order Successor:",
                                    value=min(
                                        filter(lambda x: x is not None,
                                               root.right.values)))

                filex.seek(0)
                await ctx.send(embed=embed,
                               file=discord.File(filex, "bst.png"))
            elif command.startswith("pause"):
                timeout = 86400
                await ctx.send("Timeout paused.")
            elif command.startswith("unpause"):
                timeout = 60
                await ctx.send("Timeout reset to 60 seconds.")
            elif command.startswith("show"):
                display = True
            elif command.startswith("insert"):
                add = []

                for entry in command[7:].split():
                    if re.fullmatch(r"[+-]?((\d+(\.\d*)?)|(\.\d+))", entry):
                        try:
                            num = int(entry)
                        except ValueError:
                            num = float(entry)
                    else:
                        continue

                    add.append(str(num))

                    if not get_node(num):
                        insert(root, num, highlight=True)

                await ctx.send(f"Inserted {','.join(add)}.")
                display = True
            elif command.startswith("delete"):
                remove = []

                for entry in command[7:].split():
                    try:
                        num = float(entry)
                    except Exception:
                        continue

                    if root.size == 1:
                        await ctx.send(
                            "Tree has reached one node in size. Stopping deletions."
                        )
                        break

                    if math.modf(num)[0] == 0:
                        num = int(round(num))

                    if not get_node(num):
                        continue

                    remove.append(str(num))
                    root = delete(root, num)

                await ctx.send(f"Deleted {','.join(remove)}.")
                display = True
            elif command.startswith("exit"):
                return await ctx.send("Exiting.")
            elif command.startswith("bst"):
                return
Ejemplo n.º 27
0
            for url in options_dict.values():
                if mimetypes.guess_type(url)[0] and mimetypes.guess_type(
                        url)[0].startswith("image"):
                    filex = BytesIO(requests.get(url).content)
                    filex.seek(0)
                    files.append(discord.File(filex, filename=url))

            return await ctx.send(output, files=files)

        if id_:
            return await ctx.send(
                "There's an active poll in this channel already.")

        if len(options) <= 1:
            raise BadArgs("Please enter more than one option to poll.",
                          show_help=True)
        elif len(options) > 20:
            raise BadArgs("Please limit to 10 options.")
        elif len(options) == 2 and options[0] == "yes" and options[1] == "no":
            reactions = ["✅", "❌"]
        else:
            reactions = tuple(chr(127462 + i) for i in range(26))

        description = []

        for x, option in enumerate(options):
            description += f"\n{reactions[x]}: {option}"

        embed = discord.Embed(title=question, description="".join(description))
        files = []
Ejemplo n.º 28
0
    async def poll(self, ctx: commands.Context):
        """
        `!poll` __`Poll generator`__

        **Usage:** !poll <question | check | end> | <option> | <option> | [option] | [...]

        **Examples:**
        `!poll poll | yee | nah` generates poll titled "poll" with options "yee" and "nah"
        `!poll check` returns content of last poll
        `!poll end` ends current poll in channel and returns results
        """

        poll_list = tuple(map(str.strip, ctx.message.content[6:].split("|")))
        question = poll_list[0]
        options = poll_list[1:]

        id_ = self.poll_dict.get(str(ctx.channel.id), "")

        if question in ("check", "end"):
            if end := (question == "end"):
                del self.poll_dict[str(ctx.channel.id)]
                write_json(self.poll_dict, "data/poll.json")

            if not id_:
                raise BadArgs("No active poll found.")

            try:
                poll_message = await ctx.channel.fetch_message(id_)
            except discord.NotFound:
                raise BadArgs(
                    "Looks like someone deleted the poll, or there is no active poll."
                )

            embed = poll_message.embeds[0]
            unformatted_options = [
                x.strip().split(": ") for x in embed.description.split("\n")
            ]
            options_dict = {}

            for x in unformatted_options:
                options_dict[x[0]] = x[1]

            tally = {x: 0 for x in options_dict}

            for reaction in poll_message.reactions:
                if reaction.emoji in options_dict:
                    async for reactor in reaction.users():
                        if reactor.id != self.bot.user.id:
                            tally[reaction.emoji] += 1

            output = f"{'Final' if end else 'Current'} results of the poll **\"{embed.title}\"**\nLink: {poll_message.jump_url}\n```"

            max_len = max(map(len, options_dict.values()))

            for key in tally:
                output += f"{options_dict[key].ljust(max_len)}: " + \
                          f"{('👑' * tally[key]).replace('👑', '▓', ((tally[key] - 1) or 1) - 1) if tally[key] == max(tally.values()) else '░' * tally[key]}".ljust(max(tally.values())).replace('👑👑', '👑') + \
                          f" ({tally[key]} votes, {round(tally[key] / sum(tally.values()) * 100, 2) if sum(tally.values()) else 0}%)\n\n"

            output += "```"

            files = []

            for url in options_dict.values():
                if mimetypes.guess_type(url)[0] and mimetypes.guess_type(
                        url)[0].startswith("image"):
                    filex = BytesIO(requests.get(url).content)
                    filex.seek(0)
                    files.append(discord.File(filex, filename=url))

            return await ctx.send(output, files=files)
Ejemplo n.º 29
0
class Commands(commands.Cog):
    def __init__(self, bot: commands.Bot):
        self.bot = bot
        self.add_instructor_role_counter = 0
        self.bot.d_handler = DiscordHandler()
        self.role_converter = CustomRoleConverter()

        if not isfile(POLL_FILE):
            create_file_if_not_exists(POLL_FILE)
            write_json({}, POLL_FILE)

        self.poll_dict = read_json(POLL_FILE)

        for channel in filter(lambda ch: not self.bot.get_channel(int(ch)),
                              list(self.poll_dict)):
            del self.poll_dict[channel]

        for channel in (c for g in self.bot.guilds for c in g.text_channels
                        if str(c.id) not in self.poll_dict):
            self.poll_dict.update({str(channel.id): ""})

        write_json(self.poll_dict, POLL_FILE)

    @commands.command()
    @commands.cooldown(1, 5, commands.BucketType.user)
    async def emojify(self, ctx: commands.Context):
        """
        `!emojify` __`Emoji text generator`__

        **Usage:** !emojify <text>

        **Examples:**
        `!emojify hello` prints "hello" with emoji
        `!emojify b` prints b with emoji"
        """

        mapping = {
            "A": "🇦",
            "B": "🅱",
            "C": "🇨",
            "D": "🇩",
            "E": "🇪",
            "F": "🇫",
            "G": "🇬",
            "H": "🇭",
            "I": "🇮",
            "J": "🇯",
            "K": "🇰",
            "L": "🇱",
            "M": "🇲",
            "N": "🇳",
            "O": "🇴",
            "P": "🇵",
            "Q": "🇶",
            "R": "🇷",
            "S": "🇸",
            "T": "🇹",
            "U": "🇺",
            "V": "🇻",
            "W": "🇼",
            "X": "🇽",
            "Y": "🇾",
            "Z": "🇿",
            "0": "0️⃣",
            "1": "1️⃣",
            "2": "2️⃣",
            "3": "3️⃣",
            "4": "4️⃣",
            "5": "5️⃣",
            "6": "6️⃣",
            "7": "7️⃣",
            "8": "8️⃣",
            "9": "9️⃣"
        }

        text = ctx.message.content[9:].upper()
        output = "".join(
            mapping[i] +
            (" " if i in string.ascii_uppercase else "") if i in mapping else i
            for i in text)

        await ctx.send(output)

    @commands.command()
    @commands.cooldown(1, 5, commands.BucketType.user)
    async def join(self, ctx: commands.Context, *arg: str):
        """
        `!join` __`Adds a role to yourself`__

        **Usage:** !join [role name]

        **Examples:**
        `!join Study Group` adds the Study Group role to yourself

        **Valid Roles:**
        Looking for Partners, Study Group, He/Him/His, She/Her/Hers, They/Them/Theirs, Ze/Zir/Zirs, notify
        """

        await ctx.message.delete()

        # case where role name is space separated
        name = " ".join(arg)

        # Display help if given no argument
        if not name:
            raise BadArgs("", show_help=True)

        # make sure that you can't add roles like "prof" or "ta"
        valid_roles = [
            "Looking for Partners", "Study Group", "He/Him/His",
            "She/Her/Hers", "They/Them/Theirs", "Ze/Zir/Zirs", "notify"
        ]

        # Grab the role that the user selected
        # Converters! this also makes aliases unnecessary
        try:
            role = await self.role_converter.convert(ctx, name)
        except commands.RoleNotFound:
            raise BadArgs("You can't add that role!", show_help=True)

        # Ensure that the author does not already have the role
        if role in ctx.author.roles:
            raise BadArgs("you already have that role!")

        # Special handling for roles that exist but can not be selected by a student
        if role.name not in valid_roles:
            self.add_instructor_role_counter += 1

            if self.add_instructor_role_counter > 5:
                if self.add_instructor_role_counter == 42:
                    if random.random() > 0.999:
                        raise BadArgs(
                            "Congratulations, you found the secret message. IDEK how you did it, but good job. Still can't add the instructor role though. Bummer, I know."
                        )
                elif self.add_instructor_role_counter == 69:
                    if random.random() > 0.9999:
                        raise BadArgs("nice.")
                raise BadArgs(
                    "You can't add that role, but if you try again, maybe something different will happen on the 42nd attempt"
                )
            else:
                raise BadArgs("you cannot add an instructor/invalid role!",
                              show_help=True)

        await ctx.author.add_roles(role)
        await ctx.send("role added!", delete_after=5)

    @commands.command()
    @commands.cooldown(1, 10, commands.BucketType.user)
    async def latex(self, ctx: commands.Context, *args: str):
        """
        `!latex` __`LaTeX equation render`__

        **Usage:** !latex <equation>

        **Examples:**
        `!latex \\frac{a}{b}` [img]
        """

        formula = " ".join(args).strip("\n ")

        if sm := re.match(r"```(latex|tex)", formula):
            formula = formula[6 if sm.group(1) == "tex" else 8:]

        formula = formula.strip("`")

        body = {
            "formula": formula,
            "fsize": r"30px",
            "fcolor": r"FFFFFF",
            "mode": r"0",
            "out": r"1",
            "remhost": r"quicklatex.com",
            "preamble":
            r"\usepackage{amsmath}\usepackage{amsfonts}\usepackage{amssymb}",
            "rnd": str(random.random() * 100)
        }

        try:
            img = requests.post("https://www.quicklatex.com/latex3.f",
                                data=body,
                                timeout=10)
        except (requests.ConnectionError, requests.HTTPError,
                requests.TooManyRedirects, requests.Timeout):
            raise BadArgs("Render timed out.")

        if img.status_code != 200:
            raise BadArgs("Something done goofed. Maybe check your syntax?")

        if img.text.startswith("0"):
            await ctx.send(file=discord.File(
                BytesIO(requests.get(img.text.split()[1]).content),
                "latex.png"))
        else:
            await ctx.send(" ".join(img.text.split()[5:]), delete_after=5)
Ejemplo n.º 30
0
    async def purge(self, ctx: commands.Context, amount: int, *arg: str):
        """
        `!purge` __`purges all messages satisfying conditions from the last specified number of messages in channel.`__

        Usage: !purge <amount of messages to look through> [user | string <containing string> | reactions | type]

        **Options:**
        `user` - Only deletes messages sent by this user
        `string` - Will delete messages containing following string
        `reactions` - Will remove all reactions from messages
        `type` - One of valid types

        **Valid Types:**
        `text` - Is text only? (ignores image or embeds)
        `links` - Contains links?
        `bots` - Was send by bots?
        `images` - Contains images?
        `embeds` - Contains embeds?
        `mentions` - Contains user, role or everyone/here mentions?

        **Examples:**
        `!purge 100` deletes last 100 messages in channel
        `!purge 50 abc#1234` deletes all messages sent by abc#1234 in last 50 messages
        `!purge 30 string asdf ghjk` deletes all messages containing "asdf ghjk" in last 30 messages
        """

        await ctx.message.delete()

        if not ctx.author.guild_permissions.manage_messages:
            return await ctx.send(
                "Oops! It looks like you don't have the permission to purge.",
                delete_after=5)

        if amount > 100:
            raise BadArgs("Please enter a smaller number to purge.")

        if not arg:
            total_messages = await ctx.channel.purge(limit=amount)

            await ctx.send(
                f"**{len(total_messages)}** message{'s' if len(total_messages) > 1 else ''} cleared.",
                delete_after=5)
        elif arg[0] == "reactions":
            messages = await ctx.channel.history(limit=amount).flatten()

            for i in messages:
                if i.reactions:
                    await i.clear_reactions()

            await ctx.send(
                f"Reactions removed from the last {'' if amount == 1 else '**' + str(amount) + '**'} message{'s' if amount > 1 else ''}.",
                delete_after=5)
        elif arg[0] == "text":
            total_messages = await ctx.channel.purge(
                limit=amount,
                check=lambda m: not m.embeds and not m.attachments)

            await ctx.send(
                f"**{len(total_messages)}** text message{'s' if len(total_messages) > 1 else ''} purged."
            )
        elif arg[0] == "bots":
            total_messages = await ctx.channel.purge(
                limit=amount, check=lambda m: m.author.bot)

            await ctx.send(
                f"**{len(total_messages)}** bot message{'s' if len(total_messages) > 1 else ''} purged.",
                delete_after=5)
        elif arg[0] == "images":
            total_messages = await ctx.channel.purge(
                limit=amount, check=lambda m: m.attachments)

            await ctx.send(
                f"**{len(total_messages)}** image message{'s' if len(total_messages) > 1 else ''} purged.",
                delete_after=5)
        elif arg[0] == "embeds":
            total_messages = await ctx.channel.purge(limit=amount,
                                                     check=lambda m: m.embeds)

            await ctx.send(
                f"**{len(total_messages)}** embed message{'s' if len(total_messages) > 1 else ''} purged.",
                delete_after=5)
        elif arg[0] == "mentions":
            total_messages = await ctx.channel.purge(
                limit=amount, check=lambda m: m.mentions)

            await ctx.send(
                f"**{len(total_messages)}** mention message{'s' if len(total_messages) > 1 else ''} purged.",
                delete_after=5)
        elif arg[0] == "links":
            total_messages = await ctx.channel.purge(
                limit=amount,
                check=lambda m: bool(
                    re.search(r"https?://[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+", m.
                              content)))

            await ctx.send(
                f"**{len(total_messages)}** link message{'s' if len(total_messages) > 1 else ''} purged.",
                delete_after=5)
        elif arg[0] == "string":
            total_messages = await ctx.channel.purge(
                limit=amount, check=lambda m: " ".join(arg[1:]) in m.content)

            await ctx.send(
                f"**{len(total_messages)}** message{'s' if len(total_messages) > 1 else ''} containing \"{' '.join(arg[1:])}\" purged."
            )
        else:
            try:
                user = await MemberConverter().convert(ctx, " ".join(arg))
            except BadArgument:
                return await ctx.send("That user doesn't exist.",
                                      delete_after=5)

            total_messages = await ctx.channel.purge(
                limit=amount, check=lambda m: m.author == user)

            await ctx.send(
                f"**{len(total_messages)}** message{'s' if len(total_messages) > 1 else ''} from {user.display_name} purged.",
                delete_after=5)