async def on_raw_reaction_clear( self, payload: discord.RawReactionActionEvent) -> None: channel = self.bot.get_channel(id=payload.channel_id) try: guild = channel.guild except AttributeError: # DMChannels don't have guilds return if version_info >= VersionInfo.from_str("3.4.0"): if await self.bot.cog_disabled_in_guild(self, guild): return try: msg = await channel.fetch_message(id=payload.message_id) except (discord.errors.NotFound, discord.Forbidden): return if guild.id not in self.starboards: return # starboards = await self.config.guild(guild).starboards() for name, starboard in self.starboards[guild.id].items(): # starboard = StarboardEntry.from_json(s_board) star_channel = self.bot.get_channel(starboard.channel) if not star_channel: continue async with starboard.lock: await self._loop_messages(payload, starboard, star_channel, msg, None)
async def exclusive_add(self, ctx: Context, role: RoleHierarchyConverter, *exclude: RoleHierarchyConverter): """ Add role exclusion (This will remove if the designated role is acquired if the included roles are not selfremovable they will not be removed and the designated role will not be given) `<role>` This is the role a user may acquire you want to set exclusions for. `<exclude>` The role(s) you wish to have removed when a user gains the `<role>` Note: This will only work for reaction roles and automatic roles from this cog. """ cur_setting = await self.config.role(role).exclusive_to() inclusive = await self.config.role(role).inclusive_with() for excluded_role in exclude: if excluded_role.id in inclusive: return await ctx.send( _("You cannot exclude a role that is already considered inclusive." )) if excluded_role.id not in cur_setting: cur_setting.append(excluded_role.id) await self.config.role(role).exclusive_to.set(cur_setting) roles = [ctx.guild.get_role(i) for i in cur_setting] allowed_mentions = {} role_names = humanize_list([i.name for i in roles if i]) if version_info >= VersionInfo.from_str("3.4.0"): allowed_mentions = { "allowed_mentions": discord.AllowedMentions(roles=False) } role_names = humanize_list([i.mention for i in roles if i]) await ctx.send( _("Role {role} will now remove the following roles if it " "is acquired automatically or via reaction roles.\n{excluded_roles}." ).format(role=role.name, excluded_roles=role_names), **allowed_mentions)
async def on_adventure_cart(self, ctx: commands.Context) -> None: if version_info >= VersionInfo.from_str("3.4.0"): if await self.bot.cog_disabled_in_guild(self, ctx.guild): return roles = [ f"<@&{rid}>" for rid in await self.config.guild(ctx.guild).cart_roles() ] users = [ f"<@!{uid}>" for uid in await self.config.guild(ctx.guild).cart_users() ] guild_members = [m.id for m in ctx.guild.members] all_users = await self.config.all_users() for u_id, data in all_users.items(): user_mention = f"<@!{u_id}>" if u_id in guild_members and data[ "cart"] and user_mention not in users: users.append(user_mention) if roles or users: msg = (f"{humanize_list(roles) if roles else ''} " + f"{humanize_list(users) if users else ''} " + _("A cart has arrived, come buy something!")) for page in pagify(msg): await ctx.send(page, **self.sanitize)
def __init__(self, bot): self.bot = bot self.config = Config.get_conf(self, 154497072148643840, force_registration=True) self.config.register_guild( roles=[], users=[], adventure_roles=[], adventure_users=[], cart_users=[], cart_roles=[], miniboss_users=[], miniboss_roles=[], ascended_users=[], ascended_roles=[], transcended_users=[], transcended_roles=[], immortal_users=[], immortal_roles=[], possessed_users=[], possessed_roles=[], ) self.config.register_user( adventure=False, miniboss=False, dragon=False, cart=False, ascended=False, transcended=False, immortal=False, possessed=False, ) if version_info >= VersionInfo.from_str("3.4.0"): self.sanitize = {"allowed_mentions": discord.AllowedMentions(users=True, roles=True)} else: self.sanitize = {}
async def start_stream(self) -> None: if version_info >= VersionInfo.from_str("3.2.0"): await self.bot.wait_until_red_ready() else: await self.bot.wait_until_ready() api = None base_sleep = 300 count = 1 while self.run_stream: if not await self.config.api.consumer_key(): # Don't run the loop until tokens are set await asyncio.sleep(base_sleep) continue tweet_list = list(self.accounts) if not tweet_list: await asyncio.sleep(base_sleep) continue if not api: api = await self.authenticate() if self.mystream is None: await self._start_stream(tweet_list, api) elif self.mystream and not getattr(self.mystream, "running"): count += 1 await self._start_stream(tweet_list, api) log.debug(f"tweets waiting {base_sleep * count} seconds.") await asyncio.sleep(base_sleep * count)
async def post_tweet_status( self, channel_send: discord.TextChannel, em: discord.Embed, status: tw.Status, use_custom_embed: bool = True, ): username = status.user.screen_name post_url = f"https://twitter.com/{status.user.screen_name}/status/{status.id}" if version_info >= VersionInfo.from_str("3.4.0"): if await self.bot.cog_disabled_in_guild(self, channel_send.guild): return try: if channel_send.permissions_for(channel_send.guild.me).embed_links: if use_custom_embed: await channel_send.send(post_url, embed=em) else: await channel_send.send(post_url) elif channel_send.permissions_for(channel_send.guild.me).manage_webhooks: webhook = None for hook in await channel_send.webhooks(): if hook.name == channel_send.guild.me.name: webhook = hook if webhook is None: webhook = await channel_send.create_webhook(name=channel_send.guild.me.name) avatar = status.user.profile_image_url if use_custom_embed: await webhook.send(post_url, username=username, avatar_url=avatar, embed=em) else: await webhook.send(post_url, username=username, avatar_url=avatar) else: await channel_send.send(post_url) except Exception: msg = "{0} from <#{1}>({1})".format(post_url, channel_send.id) log.error(msg, exc_info=True)
def __init__(self, bot): self.bot = bot self.config = Config.get_conf(self, identifier=144014746356678656) default_guild = { "approval_channel": None, "announcement_channel": None, "ping": "", "events": {}, "custom_links": {}, "default_max": None, "auto_end_events": False, "publish": False, } default_user = {"player_class": ""} self.config.register_guild(**default_guild) self.config.register_member(**default_user) self.event_cache = {} self.bot.loop.create_task(self.initialize()) if version_info >= VersionInfo.from_str("3.4.0"): self.sanitize = { "allowed_mentions": discord.AllowedMentions(everyone=True, roles=True) } else: self.sanitize = {}
async def on_raw_reaction_add( self, payload: discord.RawReactionActionEvent) -> None: """ Checks for reactions to the event """ if str(payload.emoji) not in EVENT_EMOJIS: # log.debug("Not a valid yes or no emoji") return if payload.guild_id not in self.event_cache: return if payload.message_id not in self.event_cache[payload.guild_id]: return guild = self.bot.get_guild(payload.guild_id) user = guild.get_member(payload.user_id) if user.bot: return if version_info >= VersionInfo.from_str("3.4.0"): if await self.bot.cog_disabled_in_guild(self, guild): return event = self.event_cache[payload.guild_id][payload.message_id] if str(payload.emoji) == "\N{WHITE HEAVY CHECK MARK}": await self.add_user_to_event(user, event) if str(payload.emoji) == "\N{WHITE QUESTION MARK ORNAMENT}": await self.add_user_to_maybe(user, event) if str(payload.emoji) == "\N{NEGATIVE SQUARED CROSS MARK}": if user == event.hoster: async with self.config.guild(guild).events() as events: event = await Event.from_json(events[str(user.id)], guild) await event.message.edit(content="This event has ended.") del events[str(user.id)] del self.event_cache[guild.id][event.message.id] return await self.remove_user_from_event(user, event)
async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent) -> None: if "content" not in payload.data: return if "guild_id" not in payload.data: return if "bot" in payload.data["author"]: return channel = self.bot.get_channel(int(payload.data["channel_id"])) try: message = await channel.fetch_message(int(payload.data["id"])) except (discord.errors.Forbidden, discord.errors.NotFound): log.debug( _("I don't have permission to read channel history or cannot find the message.") ) return except Exception: log.info("Could not find channel or message") # If we can't find the channel ignore it return if message.author.bot: # somehow we got a bot through the previous check :thonk: return if version_info >= VersionInfo.from_str("3.4.0"): if await self.bot.cog_disabled_in_guild(self, message.guild): return await self.check_triggers(message, True)
async def edit_goal(self, bot: Red, channel: discord.TextChannel, message_id: int, em: discord.Embed) -> None: try: if not channel.permissions_for(channel.guild.me).embed_links: return 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): return guild = channel.guild game_day_channels = await bot.get_cog("Hockey").config.guild( guild).gdc() role = discord.utils.get(guild.roles, name=self.team_name + " GOAL") if game_day_channels is not None: # We don't want to ping people in the game day channels twice if channel.id in game_day_channels: role = None if role is None or "missed" in self.event.lower(): await message.edit(embed=em) else: await message.edit(content=role.mention, embed=em) except (discord.errors.NotFound, discord.errors.Forbidden): return except Exception: log.exception(f"Could not edit goal in {channel=}")
async def on_message(self, message: discord.Message) -> None: guild = message.guild if not await self.check_bw_list(message): return if version_info >= VersionInfo.from_str("3.4.0"): if guild and await self.bot.cog_disabled_in_guild(self, guild): return ctx = await self.bot.get_context(message) author = message.author text = message.clean_content to_strip = f"(?m)^(<@!?{self.bot.user.id}>)" is_mention = re.search(to_strip, message.content) if is_mention: text = text[len(ctx.me.display_name) + 2:] log.debug(text) if not text: log.debug("No text to send to cleverbot.") return if guild is None: if await self.config.allow_dm( ) and message.author.id != self.bot.user.id: if ctx.prefix: return await self.send_cleverbot_response(text, message.author, ctx) return if message.author.id != self.bot.user.id: if not is_mention and message.channel.id != await self.config.guild( guild).channel(): return if not await self.config.guild(guild).toggle(): return await self.send_cleverbot_response(text, author, ctx)
async def setapikey(self, ctx, *, apikey): """Set your Riot API key for that cog to work. Note that it is safer to use this command in DM.""" if version_info >= VersionInfo.from_str("3.2.0"): key = await ctx.bot.set_shared_api_tokens("league", api_key=apikey) else: await self.bot.db.api_tokens.set_raw("league", value={'api_key': apikey}) await ctx.send("Done")
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 setup(bot): if version_info < VersionInfo.from_str("3.1.2"): raise CogLoadError( "Hey, this now depends on changes in Red 3.1.2." "\nGo update, it's a straight improvement from previously supported versions." ) discord.TextChannel.delete_messages = replacement_delete_messages bot.add_cog(Scheduler(bot))
async def _get_api_key(self): if not self.api: if version_info >= VersionInfo.from_str("3.2.0"): db = await self.bot.get_shared_api_tokens("apex") else: db = await self.bot.db.api_tokens.get_raw("apex", default=None) self.api = db['api_key'] return self.api else: return self.api
async def setapexkey(self, ctx, *, apikey): """Set your Apex API key for that cog to work. You can get one following this link: https://apex.tracker.gg/site-api and clicking "Manage or Create API Keys" Note that it is safer to use this command in DM.""" if version_info >= VersionInfo.from_str("3.2.0"): key = await ctx.bot.set_shared_api_tokens("apex", api_key=apikey) else: await self.bot.db.api_tokens.set_raw("apex", value={'api_key': apikey}) await ctx.send("Done")
async def edit(self, star_channel: discord.TextChannel, content: str) -> None: if self.new_message is None: return try: if version_info >= VersionInfo.from_str("3.4.6"): message_edit = star_channel.get_partial_message(self.new_message) else: message_edit = await star_channel.fetch_message(self.new_message) await message_edit.edit(content=content) except (discord.errors.NotFound, discord.errors.Forbidden): return
async def on_message(self, message: discord.Message) -> None: if message.guild is None: return if message.author.bot: return if version_info >= VersionInfo.from_str("3.4.0"): if await self.bot.cog_disabled_in_guild(self, message.guild): return if getattr(message, "retrigger", False): log.debug("A ReTrigger dispatched message, ignoring.") return await self.check_triggers(message, False)
async def predicate(ctx): if version_info >= VersionInfo.from_str("3.2.0"): key = await ctx.bot.get_shared_api_tokens("apex") else: key = await ctx.bot.db.api_tokens.get_raw("apex", default=None) try: res = True if key["api_key"] else False except: res = False if not res and ctx.invoked_with in dir(ctx.bot.get_cog('Apex')): raise commands.UserFeedbackCheckFailure(message="You need to set the API key using `[p]set api apex <api_key>` first !") return res
def allowed_mentions(self): if version_info >= VersionInfo.from_str("3.4.6"): return discord.AllowedMentions( everyone=self.everyone_mention, users=self.user_mention, roles=self.role_mention, replied_user=self.reply if self.reply is not None else False, ) else: return discord.AllowedMentions(everyone=self.everyone_mention, users=self.user_mention, roles=self.role_mention)
async def delete(self, star_channel: discord.TextChannel) -> None: if self.new_message is None: return try: if version_info >= VersionInfo.from_str("3.4.6"): message_edit = star_channel.get_partial_message(self.new_message) else: message_edit = await star_channel.fetch_message(self.new_message) self.new_message = None self.new_channel = None await message_edit.delete() except (discord.errors.NotFound, discord.errors.Forbidden): return
async def fetch_latest_red_version_info( ) -> Tuple[Optional[VersionInfo], Optional[str]]: try: async with aiohttp.ClientSession() as session: async with session.get( "https://pypi.org/pypi/Red-DiscordBot/json") as r: data = await r.json() except (aiohttp.ClientError, asyncio.TimeoutError): return None, None else: release = VersionInfo.from_str(data["info"]["version"]) required_python = data["info"]["requires_python"] return release, required_python
async def send_cleverbot_response(self, message: str, author: Union[discord.Member, discord.User], ctx: commands.Context) -> None: """ This is called when we actually want to send a reply """ await ctx.trigger_typing() try: response = await self.get_response(author, message) except NoCredentials: msg = _("The owner needs to set the credentials first.\n" "See: [p]cleverbotset apikey") await ctx.send(msg) except APIError as e: await ctx.send("Error contacting the API. Error code: {}".format(e) ) except InvalidCredentials: msg = _("The token that has been set is not valid.\n" "See: [p]cleverbotset") await ctx.send(msg) except OutOfRequests: msg = _("You have ran out of requests for this month. " "The free tier has a 5000 requests a month limit.") await ctx.send(msg) else: replies = version_info >= VersionInfo.from_str("3.4.6") if ctx.guild: replies = replies or await self.config.guild(ctx.guild).reply() if await self.config.guild(ctx.guild).mention(): if replies: await ctx.send(response, reference=ctx.message, mention_author=True) else: await ctx.send(f"{author.mention} {response}") else: if replies: await ctx.send(response, reference=ctx.message, mention_author=False) else: await ctx.send(response) else: if replies: await ctx.send(response, reference=ctx.message, mention_author=False) else: await ctx.send(response)
async def _invite_url(self) -> str: if not version_info.dev_release and version_info >= VersionInfo.from_str( "3.4.16"): return await self.bot.get_invite_url() # This is all for backwards compatibility # Even if a method for this gets added it would be redundant considering # `bot.get_invite_url` exists in the latest red versions app_info = await self.bot.application_info() data = await self.bot._config.all() commands_scope = data["invite_commands_scope"] scopes = ("bot", "applications.commands") if commands_scope else None perms_int = data["invite_perm"] permissions = discord.Permissions(perms_int) return discord.utils.oauth_url(app_info.id, permissions, scopes=scopes)
async def on_message(self, message: discord.Message) -> None: guild = message.guild if not guild: return if version_info >= VersionInfo.from_str("3.4.0"): if await self.bot.cog_disabled_in_guild(self, guild): return user = cast(discord.Member, message.author) channel = message.channel agree_channel = cast( discord.TextChannel, guild.get_channel(await self.config.guild(guild).AGREE_CHANNEL())) if guild is None: return if agree_channel is None: return if channel.id != agree_channel.id: return if user.bot: return if user.id in self.users: if not guild.me.guild_permissions.manage_roles: await self._no_perms(agree_channel) return if self.users[user.id]["key"].lower() in message.content.lower(): perms = agree_channel.permissions_for(guild.me) roles_id = await self.config.guild(guild).ROLE() roles = [role for role in guild.roles if role.id in roles_id] for role in roles: await user.add_roles(role, reason=_("Agreed to the rules")) if perms.manage_messages and await self.config.guild( guild).DELETE_KEY(): try: await message.delete() except Exception: pass if self.users[user.id]["message"].guild: try: await self.users[user.id]["message"].delete() except Exception: pass elif perms.add_reactions: await message.add_reaction("✅") del self.users[user.id]
async def on_raw_reaction_clear(self, payload: discord.RawReactionActionEvent) -> None: await self.ready.wait() guild = self.bot.get_guild(payload.guild_id) if not guild: return if version_info >= VersionInfo.from_str("3.4.0"): if await self.bot.cog_disabled_in_guild(self, guild): return if guild.id not in self.starboards: return # starboards = await self.config.guild(guild).starboards() for name, starboard in self.starboards[guild.id].items(): # starboard = StarboardEntry.from_json(s_board) star_channel = guild.get_channel(starboard.channel) if not star_channel: continue async with starboard.lock: await self._loop_messages(payload, starboard, star_channel)
async def global_perms(self, message: discord.Message) -> bool: """Check the user is/isn't globally whitelisted/blacklisted. https://github.com/Cog-Creators/Red-DiscordBot/blob/V3/release/3.0.0/redbot/core/global_checks.py """ if version_info >= VersionInfo.from_str("3.3.6"): if not await self.bot.ignored_channel_or_guild(message): return False if await self.bot.is_owner(message.author): return True try: return await self.bot.allowed_by_whitelist_blacklist(message.author ) except AttributeError: whitelist = await self.bot.db.whitelist() if whitelist: return message.author.id in whitelist return message.author.id not in await self.bot.db.blacklist()
async def bot_welcome(self, member: discord.Member, guild: discord.Guild): bot_welcome = await self.config.guild(guild).BOTS_MSG() bot_role = await self.config.guild(guild).BOTS_ROLE() msg = bot_welcome or rand_choice(await self.config.guild(guild).GREETING()) channel = await self.get_welcome_channel(member, guild) is_embed = await self.config.guild(guild).EMBED() if version_info >= VersionInfo.from_str("3.4.0"): mentions = await self.config.guild(guild).MENTIONS() sanitize = { "allowed_mentions": discord.AllowedMentions(**mentions) } else: sanitize = {} if bot_role: try: role = cast(discord.abc.Snowflake, guild.get_role(bot_role)) await member.add_roles(role, reason=_("Automatic Bot Role")) except Exception: log.error( _("welcome.py: unable to add a role. ") + f"{bot_role} {member}", exc_info=True, ) else: log.debug( _("welcome.py: added ") + str(role) + _(" role to ") + _("bot, ") + str(member)) if bot_welcome: # finally, welcome them if not channel: return if is_embed and channel.permissions_for(guild.me).embed_links: em = await self.make_embed(member, guild, msg, False) if await self.config.guild(guild).EMBED_DATA.mention(): await channel.send(member.mention, embed=em, **sanitize) else: await channel.send(embed=em) else: await channel.send( await self.convert_parms(member, guild, bot_welcome, False), **sanitize)
async def on_message(self, message: discord.Message) -> None: guild = message.guild if message.author.bot: return if not await self.check_bw_list(message): return if version_info >= VersionInfo.from_str("3.4.0"): if guild and await self.bot.cog_disabled_in_guild(self, guild): return ctx = await self.bot.get_context(message) author = message.author text = message.clean_content to_strip = f"(?m)^(<@!?{self.bot.user.id}>)" is_mention = re.search(to_strip, message.content) is_reply = False reply = getattr(message, "reference", None) if reply and (reference := getattr(reply, "resolved")) is not None: author = getattr(reference, "author") if author is not None: is_reply = reference.author.id == self.bot.user.id
async def on_member_remove(self, member: discord.Member): guild = member.guild if version_info >= VersionInfo.from_str("3.4.0"): if await self.bot.cog_disabled_in_guild(self, guild): return sticky_roles = await self.config.guild(guild).sticky_roles() to_reapply = await self.config.guild(guild).to_reapply() if sticky_roles is None: return save = False for role in member.roles: if role.id in sticky_roles: if str(member.id) not in to_reapply: to_reapply[str(member.id)] = [] to_reapply[str(member.id)].append(role.id) save = True if save: await self.config.guild(guild).to_reapply.set(to_reapply)