Exemple #1
0
    async def assignment_reminder(self) -> None:
        while True:
            for ch in filter(operator.attrgetter("live_channels"),
                             self.bot.d_handler.canvas_handlers):
                notify_role = next(
                    (r for r in ch.guild.roles if r.name.lower() == "notify"),
                    None)

                for c in ch.courses:
                    for time in ("week", "day"):
                        data_list = ch.get_assignments(f"1-{time}",
                                                       (str(c.id), ),
                                                       CANVAS_API_URL)

                        if time == "week":
                            recorded_ass_ids = ch.due_week[str(c.id)]
                        else:
                            recorded_ass_ids = ch.due_day[str(c.id)]

                        ass_ids = await self._assignment_sender(
                            ch, data_list, recorded_ass_ids, notify_role, time)

                        if time == "week":
                            ch.due_week[str(c.id)] = ass_ids
                        else:
                            ch.due_day[str(c.id)] = ass_ids

                        self.canvas_dict[str(ch.guild.id)][f"due_{time}"][str(
                            c.id)] = ass_ids

                    write_json(self.canvas_dict, "data/canvas.json")

            await asyncio.sleep(30)
Exemple #2
0
    async def pinit(self, ctx: commands.Context, name: str, pid: str):
        """
        `!pinit` _ `course name` _ _ `piazza id` _

        **Usage:** !pinit <course name> <piazza id>

        **Examples:**
        `!pinit CPSC221 abcdef1234` creates a CPSC221 Piazza instance for the server

        *Only usable by TAs and Profs
        """

        self.bot.d_handler.piazza_handler = PiazzaHandler(
            name, pid, PIAZZA_EMAIL, PIAZZA_PASSWORD, ctx.guild)

        # dict.get default to empty list so KeyError is never thrown
        for channel in self.piazza_dict.get("channels", []):
            self.bot.d_handler.piazza_handler.add_channel(channel)

        self.piazza_dict["channels"] = [ctx.channel.id]
        self.piazza_dict["course_name"] = name
        self.piazza_dict["piazza_id"] = pid
        self.piazza_dict["guild_id"] = ctx.guild.id
        write_json(self.piazza_dict, "data/piazza.json")
        response = f"Piazza instance created!\nName: {name}\nPiazza ID: {pid}\n"
        response += "If the above doesn't look right, please use `!pinit` again with the correct arguments"
        await ctx.send(response)
Exemple #3
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
            ]
            write_json(self.canvas_dict, "data/canvas.json")

            for course in c_handler.courses:
                watchers_file = f"{util.canvas_handler.COURSES_DIRECTORY}/{course.id}/watchers.txt"
                c_handler.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.")
Exemple #4
0
    def __init__(self, bot: commands.Bot):
        self.bot = bot

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

        self.piazza_dict = read_json(PIAZZA_FILE)
Exemple #5
0
    def __init__(self, bot: commands.Bot):
        self.bot = bot

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

        self.canvas_dict = read_json(CANVAS_FILE)
Exemple #6
0
 def _add_guild(self, guild: discord.Guild) -> None:
     if guild not in (ch.guild
                      for ch in self.bot.d_handler.canvas_handlers):
         self.bot.d_handler.canvas_handlers.append(
             CanvasHandler(CANVAS_API_URL, CANVAS_API_KEY, guild))
         self.canvas_dict[str(guild.id)] = {
             "courses": [],
             "live_channels": [],
             "due_week": {},
             "due_day": {}
         }
         write_json(self.canvas_dict, "data/canvas.json")
Exemple #7
0
    async def send_canvas_track_msg(self, c_handler: CanvasHandler,
                                    ctx: commands.Context) -> None:
        """
        Sends an embed to ctx that lists the Canvas courses being tracked by c_handler.
        """

        guild_dict = self.canvas_dict[str(ctx.message.guild.id)]
        guild_dict["courses"] = [str(c.id) for c in c_handler.courses]
        guild_dict["due_week"] = c_handler.due_week
        guild_dict["due_day"] = c_handler.due_day

        write_json(self.canvas_dict, "data/canvas.json")

        embed_var = self._get_tracking_courses(c_handler)
        embed_var.set_footer(text=f"Requested by {ctx.author.display_name}",
                             icon_url=str(ctx.author.avatar_url))
        await ctx.send(embed=embed_var)
Exemple #8
0
    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)
Exemple #9
0
    async def puntrack(self, ctx: commands.Context):
        """
        `!puntrack` __`Untracks Piazza posts in channel`__

        **Usage:** !puntrack

        **Examples:**
        `!puntrack` removes the current channel's id to the Piazza instance's list of channels

        The channels removed through `!puntrack` are where send_pupdate and track_inotes send their responses.

        *Only usable by TAs and Profs
        """

        cid = ctx.message.channel.id

        self.bot.d_handler.piazza_handler.remove_channel(cid)
        self.piazza_dict[
            "channels"] = self.bot.d_handler.piazza_handler.channels
        write_json(self.piazza_dict, "data/piazza.json")
        await ctx.send("Channel removed from tracking!")
Exemple #10
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"
                c_handler.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:
                    c_handler.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
            ]
            write_json(self.canvas_dict, "data/canvas.json")

            await ctx.send("Added channel to live tracking.")
        else:
            await ctx.send("Channel already live tracking.")
Exemple #11
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)
Exemple #12
0
        files = []

        for url in options:
            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))

        react_message = await ctx.send(embed=embed, files=files)

        for reaction in reactions[:len(options)]:
            await react_message.add_reaction(reaction)

        self.poll_dict[str(ctx.channel.id)] = react_message.id
        write_json(self.poll_dict, "data/poll.json")

    @commands.command()
    @commands.cooldown(1, 5, commands.BucketType.user)
    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