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.")
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]
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")
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
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.")
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)