async def get_channel_obj(bot: Red, channel_id: int, data: dict) -> Optional[discord.TextChannel]: """ Requires a bot object to access config, channel_id, and channel config data Returns the channel object and sets the guild ID if it's missing from config This is used in Game objects and Goal objects so it's here to be shared between the two rather than duplicating the code """ if not data["guild_id"]: channel = bot.get_channel(id=channel_id) if not channel: await bot.get_cog("Hockey").config.channel_from_id(channel_id ).clear() log.info( f"{channel_id} channel was removed because it no longer exists" ) return None guild = channel.guild await bot.get_cog("Hockey").config.channel(channel).guild_id.set( guild.id) return channel guild = bot.get_guild(data["guild_id"]) if not guild: await bot.get_cog("Hockey").config.channel_from_id(channel_id).clear() log.info( f"{channel_id} channel was removed because it no longer exists") return None channel = guild.get_channel(channel_id) if channel is None: await bot.get_cog("Hockey").config.channel_from_id(channel_id).clear() log.info( f"{channel_id} channel was removed because it no longer exists") return None return channel
async def edit_team_goal(self, bot: Red, game_data: Game, og_msg: Tuple[int, int, int]) -> None: """ When a goal scorer has changed we want to edit the original post """ # scorer = self.headshots.format(goal["players"][0]["player"]["id"]) # post_state = ["all", game_data.home_team, game_data.away_team] em = await self.goal_post_embed(game_data) async for guild_id, channel_id, message_id in AsyncIter(og_msg, steps=100): guild = bot.get_guild(guild_id) if not guild: continue channel = guild.get_channel(int(channel_id)) if channel is None: continue bot.loop.create_task(self.edit_goal(bot, channel, message_id, em)) # This is to prevent endlessly waiting incase someone # decided to publish one of our messages we want to edit # if we did bounded_gather here the gather would wait until # rate limits are up for editing that one message # in this case we can send off the task to do it's thing # and forget about it. If one never finishes I don't care return
def check_channel(self, bot: Red, channel: discord.TextChannel) -> bool: """ Checks if the channel is allowed to track starboard messages Parameters ---------- bot: Red The bot object channel: discord.TextChannel The channel we want to verify we're allowed to post in Returns ------- bool Whether or not the channel we got a "star" in we're allowed to repost. """ guild = bot.get_guild(self.guild) if channel.is_nsfw() and not guild.get_channel(self.channel).is_nsfw(): return False if self.whitelist: if channel.id in self.whitelist: return True if channel.category_id and channel.category_id in self.whitelist: return True return False else: if channel.id in self.blacklist: return False if channel.category_id and channel.category_id in self.blacklist: return False return True
async def update_count(self, bot: Red, starboard: StarboardEntry, remove: Optional[int]) -> None: """ This function can pull the most accurate reaction info from a starboarded message However it takes at least 2 API calls which can be expensive. I am leaving This here for future potential needs but we should instead rely on our listener to keep track of reactions added/removed. Parameters ---------- bot: Red The bot object used for bot.get_guild starbaord: StarboardEntry The starboard object which contains this message entry remove: Optional[int] This was used to represent a user who removed their reaction. Returns ------- MessageEntry Returns itself although since this is handled in memory is not required. """ guild = bot.get_guild(self.guild) # log.debug(f"{guild=} {self.guild=}") orig_channel = guild.get_channel(self.original_channel) new_channel = guild.get_channel(self.new_channel) orig_reaction = [] if orig_channel: try: orig_msg = await orig_channel.fetch_message( self.original_message) orig_reaction = [ r for r in orig_msg.reactions if str(r.emoji) == str(starboard.emoji) ] except discord.HTTPException: pass new_reaction = [] if new_channel: try: new_msg = await new_channel.fetch_message(self.new_message) new_reaction = [ r for r in new_msg.reactions if str(r.emoji) == str(starboard.emoji) ] except discord.HTTPException: pass reactions = orig_reaction + new_reaction for reaction in reactions: async for user in reaction.users(): if not starboard.check_roles(user): continue if not starboard.selfstar and user.id == orig_msg.author.id: continue if user.id not in self.reactions and not user.bot: self.reactions.append(user.id) if remove and remove in self.reactions: self.reactions.remove(remove) self.reactions = list(set(self.reactions)) return self
async def remove_goal_post(bot: Red, goal: str, team: str, data: Game) -> None: """ Attempt to delete a goal if it was pulled back """ config = bot.get_cog("Hockey").config team_list = await config.teams() team_data = await get_team(bot, team) if goal not in [goal.goal_id for goal in data.goals]: try: old_msgs = team_data["goal_id"][goal]["messages"] except KeyError: return except Exception: log.exception("Error iterating saved goals") return for guild_id, channel_id, message_id in old_msgs: guild = bot.get_guild(guild_id) if not guild: continue channel = guild.get_channel(int(channel_id)) if channel and channel.permissions_for( channel.guild.me).read_message_history: try: if version_info >= VersionInfo.from_str("3.4.6"): message = channel.get_partial_message(message_id) else: message = await channel.fetch_message(message_id) except (discord.errors.NotFound, discord.errors.Forbidden): continue except Exception: log.exception( f"Error getting old goal for {str(team)} {str(goal)} in " f"{guild_id=} {channel_id=}") pass if message is not None: try: await message.delete() except (discord.errors.NotFound, discord.errors.Forbidden): pass except Exception: log.exception( f"Error getting old goal for {str(team)} {str(goal)} in " f"{guild_id=} {channel_id=}") else: log.debug( "Channel does not have permission to read history") try: team_list.remove(team_data) del team_data["goal_id"][goal] team_list.append(team_data) await config.teams.set(team_list) except Exception: log.exception("Error removing teams goals") return return
async def from_json(cls, mod_channel: discord.TextChannel, bot: Red, data: dict): """Get a Case object from the provided information Parameters ---------- mod_channel: discord.TextChannel The mod log channel for the guild bot: Red The bot's instance. Needed to get the target user data: dict The JSON representation of the case to be gotten Returns ------- Case The case object for the requested case Raises ------ `discord.NotFound` The user the case is for no longer exists `discord.Forbidden` Cannot read message history to fetch the original message. `discord.HTTPException` A generic API issue """ guild = mod_channel.guild if data["message"]: try: message = await mod_channel.get_message(data["message"]) except discord.NotFound: message = None user = await bot.get_user_info(data["user"]) moderator = guild.get_member(data["moderator"]) channel = guild.get_channel(data["channel"]) amended_by = guild.get_member(data["amended_by"]) case_guild = bot.get_guild(data["guild"]) return cls( bot=bot, guild=case_guild, created_at=data["created_at"], action_type=data["action_type"], user=user, moderator=moderator, case_number=data["case_number"], reason=data["reason"], until=data["until"], channel=channel, amended_by=amended_by, modified_at=data["modified_at"], message=message, )
async def get_ctx(self, bot: Red) -> Optional[commands.Context]: """ Returns the context object for the events message This can't be used to invoke another command but it is useful to get a basis for an events final posted message. """ guild = bot.get_guild(self.guild) if not guild: return None chan = guild.get_channel(self.channel) if not chan: return None try: msg = await chan.fetch_message(self.message) except (discord.errors.NotFound, discord.errors.Forbidden): return None return await bot.get_context(msg)
async def from_json(cls, mod_channel: discord.TextChannel, bot: Red, data: dict): """Get a Case object from the provided information Parameters ---------- mod_channel: discord.TextChannel The mod log channel for the guild bot: Red The bot's instance. Needed to get the target user data: dict The JSON representation of the case to be gotten Returns ------- Case The case object for the requested case """ guild = mod_channel.guild message = await mod_channel.get_message(data["message"]) user = await bot.get_user_info(data["user"]) moderator = guild.get_member(data["moderator"]) channel = guild.get_channel(data["channel"]) amended_by = guild.get_member(data["amended_by"]) case_guild = bot.get_guild(data["guild"]) return cls( bot=bot, guild=case_guild, created_at=data["created_at"], action_type=data["action_type"], user=user, moderator=moderator, case_number=data["case_number"], reason=data["reason"], until=data["until"], channel=channel, amended_by=amended_by, modified_at=data["modified_at"], message=message, )
async def from_json( cls, mod_channel: discord.TextChannel, bot: Red, case_number: int, data: dict, **kwargs ): """Get a Case object from the provided information Parameters ---------- mod_channel: discord.TextChannel The mod log channel for the guild bot: Red The bot's instance. Needed to get the target user case_number: int The case's number. data: dict The JSON representation of the case to be gotten **kwargs Extra attributes for the Case instance which override values in the data dict. These should be complete objects and not IDs, where possible. Returns ------- Case The case object for the requested case Raises ------ `discord.NotFound` The user the case is for no longer exists `discord.Forbidden` Cannot read message history to fetch the original message. `discord.HTTPException` A generic API issue """ guild = kwargs.get("guild") or mod_channel.guild message = kwargs.get("message") if message is None: message_id = data.get("message") if message_id is not None: message = discord.utils.get(bot.cached_messages, id=message_id) if message is None: try: message = await mod_channel.fetch_message(message_id) except discord.HTTPException: message = None else: message = None user_objects = {"user": None, "moderator": None, "amended_by": None} for user_key in tuple(user_objects): user_object = kwargs.get(user_key) if user_object is None: user_id = data.get(user_key) if user_id is None: user_object = None else: user_object = bot.get_user(user_id) or user_id user_objects[user_key] = user_object channel = kwargs.get("channel") or guild.get_channel(data["channel"]) or data["channel"] case_guild = kwargs.get("guild") or bot.get_guild(data["guild"]) return cls( bot=bot, guild=case_guild, created_at=data["created_at"], action_type=data["action_type"], case_number=case_number, reason=data["reason"], until=data["until"], channel=channel, modified_at=data["modified_at"], message=message, last_known_username=data.get("last_known_username"), **user_objects, )