Esempio n. 1
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.")
Esempio n. 2
0
    def canvas_init(self):
        for c_handler_guild_id in self.canvas_dict:
            guild = self.bot.guilds[[guild.id for guild in self.bot.guilds
                                     ].index(int(c_handler_guild_id))]

            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))

            c_handler = self._get_canvas_handler(guild)
            c_handler.track_course(
                tuple(self.canvas_dict[c_handler_guild_id]["courses"]),
                self.bot.notify_unpublished)
            live_channels_ids = self.canvas_dict[c_handler_guild_id][
                "live_channels"]
            live_channels = list(
                filter(lambda channel: channel.id in live_channels_ids,
                       guild.text_channels))
            c_handler.live_channels = live_channels

            for due in ("due_week", "due_day"):
                for c in self.canvas_dict[c_handler_guild_id][due]:
                    if due == "due_week":
                        c_handler.due_week[c] = self.canvas_dict[
                            c_handler_guild_id][due][c]
                    else:
                        c_handler.due_day[c] = self.canvas_dict[
                            c_handler_guild_id][due][c]
Esempio n. 3
0
 def _add_guild(self, guild: discord.Guild):
     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": {}
         }
         writeJSON(self.canvas_dict, "data/canvas.json")
Esempio n. 4
0
    def _get_tracking_courses(self, c_handler: CanvasHandler) -> discord.Embed:
        course_names = c_handler.get_course_names(CANVAS_API_URL)
        embed_var = discord.Embed(title="Tracking Courses:",
                                  color=CANVAS_COLOR,
                                  timestamp=datetime.utcnow())
        embed_var.set_thumbnail(url=CANVAS_THUMBNAIL_URL)

        for c_name in course_names:
            embed_var.add_field(name=c_name[0],
                                value=f"[Course Page]({c_name[1]})")

        return embed_var
Esempio n. 5
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.")
Esempio n. 6
0
    async def check_modules(self):
        """
        For every folder in handler.canvas_handler.COURSES_DIRECTORY (abbreviated as CDIR) we will:
        - get the modules for the Canvas course with ID that matches the folder name
        - compare the modules we retrieved with the modules found in CDIR/{course_id}/modules.txt
        - send the names of any new modules (i.e. modules that are not in modules.txt) to all channels
          in CDIR/{course_id}/watchers.txt
        - update CDIR/{course_id}/modules.txt with the modules we retrieved from Canvas

        NOTE: the Canvas API distinguishes between a Module and a ModuleItem. In our documentation, though,
        the word "module" can refer to both; we do not distinguish between the two types.
        """
        def get_field_value(module: Union[Module, ModuleItem]) -> str:
            """
            This function returns a string that can be added to a Discord embed as a field's value. The returned
            string contains the module's name/title attribute (depending on which one it has), as well
            as a hyperlink to the module (if the module has the html_url attribute). If the module's name/title exceeds
            MAX_MODULE_IDENTIFIER_LENGTH characters, we truncate it and append an ellipsis (...) so that the name/title
            has MAX_MODULE_IDENTIFIER_LENGTH characters.
            """

            if hasattr(module, "title"):
                field = module.title
            else:
                field = module.name

            if len(field) > MAX_MODULE_IDENTIFIER_LENGTH:
                field = f"{field[:MAX_MODULE_IDENTIFIER_LENGTH - 3]}..."

            if hasattr(module, "html_url"):
                field = f"[{field}]({module.html_url})"

            return field

        def update_embed(embed: discord.Embed, module: Union[Module,
                                                             ModuleItem],
                         embed_list: List[discord.Embed]):
            """
            Adds a field to embed containing information about given module. The field includes the module's name or
            title, as well as a hyperlink to the module if one exists.

            If the module's identifier (its name or title) has over MAX_IDENTIFIER_LENGTH characters, we truncate the
            identifier and append an ellipsis (...) so that the length does not exceed the maximum.

            The embed object that is passed in must have at most 24 fields.

            A deep copy of the embed object is appended to embed_list in two cases:
            - if adding the new field will cause the embed to exceed EMBED_CHAR_LIMIT characters in length
            - if the embed has 25 fields after adding the new field
            In both cases, we clear all of the original embed's fields after adding the embed copy to embed_list.

            NOTE: changes to embed and embed_list will persist outside this function.
            """

            field_value = get_field_value(module)

            # Note: 11 is the length of the string "Module Item"
            if 11 + len(field_value) + len(embed) > EMBED_CHAR_LIMIT:
                embed_list.append(copy.deepcopy(embed))
                embed.clear_fields()
                embed.title = f"New modules found for {course.name} (continued):"

            if isinstance(module, Module):
                embed.add_field(name="Module", value=field_value, inline=False)
            else:
                embed.add_field(name="Module Item",
                                value=field_value,
                                inline=False)

            if len(embed.fields) == 25:
                embed_list.append(copy.deepcopy(embed))
                embed.clear_fields()
                embed.title = f"New modules found for {course.name} (continued):"

        def write_modules(file_path: str, modules: List[Union[Module,
                                                              ModuleItem]]):
            """
            Stores the ID of all modules in file with given path.
            """

            with open(file_path, "w") as f:
                for module in modules:
                    f.write(str(module.id) + "\n")

        def get_embeds(
                modules: List[Union[Module,
                                    ModuleItem]]) -> List[discord.Embed]:
            """
            Returns a list of Discord embeds to send to live channels.
            """

            embed = discord.Embed(
                title=f"New modules found for {course.name}:",
                color=CANVAS_COLOR)
            embed.set_thumbnail(url=CANVAS_THUMBNAIL_URL)

            embed_list = []

            for module in modules:
                update_embed(embed, module, embed_list)

            if len(embed.fields) != 0:
                embed_list.append(embed)

            return embed_list

        if os.path.exists(util.canvas_handler.COURSES_DIRECTORY):
            courses = [
                name
                for name in os.listdir(util.canvas_handler.COURSES_DIRECTORY)
            ]

            # each folder in the courses directory is named with a course id (which is a positive integer)
            for course_id_str in courses:
                if course_id_str.isdigit():
                    course_id = int(course_id_str)

                    try:
                        course = CANVAS_INSTANCE.get_course(course_id)
                        modules_file = f"{util.canvas_handler.COURSES_DIRECTORY}/{course_id}/modules.txt"
                        watchers_file = f"{util.canvas_handler.COURSES_DIRECTORY}/{course_id}/watchers.txt"

                        create_file_if_not_exists(modules_file)
                        create_file_if_not_exists(watchers_file)

                        with open(modules_file, "r") as m:
                            existing_modules = set(m.read().splitlines())

                        all_modules = CanvasHandler.get_all_modules(
                            course, self.bot.notify_unpublished)
                        write_modules(modules_file, all_modules)
                        differences = list(
                            filter(
                                lambda module: str(module.id) not in
                                existing_modules, all_modules))

                        embeds_to_send = get_embeds(differences)

                        if embeds_to_send:
                            with open(watchers_file, "r") as w:
                                for channel_id in w:
                                    channel = self.bot.get_channel(
                                        int(channel_id.rstrip()))
                                    notify_role = next(
                                        (r for r in channel.guild.roles
                                         if r.name.lower() == "notify"), None)
                                    await channel.send(notify_role.mention
                                                       if notify_role else "")

                                    for element in embeds_to_send:
                                        await channel.send(embed=element)
                    except Exception:
                        print(traceback.format_exc(), flush=True)