from typing import Literal import discord from redbot.core.bot import Red from redbot.core import commands, checks, Config DEFAULT_OFFLINE_EMOJI = "\N{LARGE RED CIRCLE}" DEFAULT_ONLINE_EMOJI = "\N{WHITE HEAVY CHECK MARK}" class Otherbot(commands.Cog): __author__ = ["aikaterna", "Predä 。#1001"] __version__ = "0.11" async def red_delete_data_for_user( self, *, requester: Literal["discord", "owner", "user", "user_strict"], user_id: int, ): if requester == "discord": # user is deleted, just comply data = await self.config.all_guilds() for guild_id, guild_data in data.items(): if user_id in guild_data.get("watching", []): bypass = guild_data.get("watching", []) bypass = set(bypass) bypass.discard(user_id) await self.config.guild_from_id(guild_id).bypass.set(list(bypass)) def __init__(self, bot: Red): self.bot = bot self.config = Config.get_conf(self, 2730321001, force_registration=True) self.config.register_guild( ping=None, reporting=None, watching=[], online_watching=[], offline_emoji=DEFAULT_OFFLINE_EMOJI, online_emoji=DEFAULT_ONLINE_EMOJI, embed_offline=True, embed_online=True, ) async def generate_cache(self): self.otherbot_cache = await self.config.all_guilds() def cog_unload(self): self.otherbot_cache.clear() async def get_watching(self, watch_list: list, watch_type: str, guild: int): data = [] for user_id in watch_list: user = self.bot.get_user(user_id) if not user: async with self.config.guild_from_id(guild).all() as config: config[watch_type].remove(user_id) else: data.append(user.mention) return data @commands.group() @commands.guild_only() @checks.admin_or_permissions(manage_roles=True) async def otherbot(self, ctx: commands.Context): """Otherbot configuration options.""" # Following logic from Trusty's welcome cog: # https://github.com/TrustyJAID/Trusty-cogs/blob/master/welcome/welcome.py#L81 guild = ctx.guild if not ctx.invoked_subcommand: guild_data = await self.config.guild(guild).all() settings_name = dict( ping="Ping role", reporting="Channel reporting", watching="Offline tracking", online_watching="Online tracking", offline_emoji="Offline emoji", online_emoji="Online emoji", embed_offline="Offline embed", embed_online="Online embed", ) msg = "" if ctx.channel.permissions_for(ctx.me).embed_links: em = discord.Embed( color=await ctx.embed_colour(), title=f"Otherbot settings for {guild.name}" ) for attr, name in settings_name.items(): if attr == "ping": role = guild.get_role(guild_data["ping"]) if role: msg += f"**{name}**: {role.mention}\n" else: msg += f"**{name}**: Not set.\n" elif attr == "reporting": channel = guild.get_channel(guild_data["reporting"]) if channel: msg += f"**{name}**: {channel.mention}\n" else: msg += f"**{name}**: Not set.\n" elif attr == "watching": if guild_data["watching"]: msg += ( f"**{name}**: " + " ".join( await self.get_watching( guild_data["watching"], "watching", guild.id ) ) + "\n" ) else: msg += f"**{name}**: Not set.\n" elif attr == "online_watching": if guild_data["online_watching"]: msg += ( f"**{name}**: " + " ".join( await self.get_watching( guild_data["online_watching"], "online_watching", guild.id ) ) + "\n" ) else: msg += f"**{name}**: Not set.\n" else: msg += f"**{name}**: {guild_data[attr]}\n" em.description = msg if guild.icon: em.set_thumbnail(url=guild.icon.url) await ctx.send(embed=em) else: msg = "```\n" for attr, name in settings_name.items(): if attr == "ping": role = guild.get_role(guild_data["ping"]) if role: msg += f"{name}: {role.mention}\n" else: msg += f"{name}: Not set.\n" elif attr == "reporting": channel = guild.get_channel(guild_data["reporting"]) if channel: msg += f"{name}: {channel.mention}\n" else: msg += f"{name}: Not set.\n" elif attr == "watching": if guild_data["watching"]: msg += ( f"{name}: " + " ".join( await self.get_watching( guild_data["watching"], "watching", guild.id ) ) + "\n" ) else: msg += f"{name}: Not set." elif attr == "online_watching": if guild_data["online_watching"]: msg += ( f"{name}: " + " ".join( await self.get_watching( guild_data["online_watching"], "online_watching", guild.id ) ) + "\n" ) else: msg += f"{name}: Not set.\n" else: msg += f"**{name}**: {guild_data[attr]}\n" msg += "```" await ctx.send(msg) @otherbot.command() async def channel(self, ctx: commands.Context, channel: discord.TextChannel = None): """ Sets the channel to report in. Default to the current one. """ if not channel: channel = ctx.channel await self.config.guild(ctx.guild).reporting.set(channel.id) await ctx.send(f"Reporting channel set to: {channel.mention}.") await self.generate_cache() @otherbot.command() async def pingrole(self, ctx: commands.Context, role_name: discord.Role = None): """Sets the role to use for pinging. Leave blank to reset it.""" if not role_name: await self.config.guild(ctx.guild).ping.set(None) return await ctx.send("Ping role cleared.") await self.config.guild(ctx.guild).ping.set(role_name.id) pingrole_id = await self.config.guild(ctx.guild).ping() pingrole_obj = discord.utils.get(ctx.guild.roles, id=pingrole_id) await ctx.send(f"Ping role set to: `{pingrole_obj.name}`.") await self.generate_cache() @otherbot.group(name="watch", aliases=["watching"]) async def otherbot_watch(self, ctx: commands.Context): """Watch settings.""" @otherbot_watch.group(name="offline") async def otherbot_watch_offline(self, ctx: commands.Context): """Manage offline notifications.""" @otherbot_watch_offline.command(name="add") async def otherbot_watch_offline_add(self, ctx: commands.Context, bot: discord.Member): """Add a bot that will be tracked when it goes offline.""" if not bot.bot: return await ctx.send( "You can't track normal users. Please try again with a bot user." ) async with self.config.guild(ctx.guild).watching() as watch_list: watch_list.append(bot.id) await ctx.send(f"I will now track {bot.mention} when it goes offline.") await self.generate_cache() @otherbot_watch_offline.command(name="remove") async def otherbot_watch_offline_remove(self, ctx: commands.Context, bot: discord.Member): """Removes a bot currently tracked.""" if not bot.bot: return await ctx.send( "You can't choose a normal user. Please try again with a bot user." ) async with self.config.guild(ctx.guild).watching() as watch_list: try: watch_list.remove(bot.id) await ctx.send( f"Successfully removed {bot.mention} from offline tracked bot list." ) except ValueError: await ctx.send(f"{bot.mention} is not currently tracked.") await self.generate_cache() @otherbot_watch_offline.command(name="list") async def otherbot_watch_offline_list(self, ctx: commands.Context): """Lists currently tracked bots.""" watching = await self.config.guild(ctx.guild).watching() if not watching: return await ctx.send("There is currently no bots tracked for offline status.") watching_list = await self.get_watching(watching, "watching", ctx.guild.id) await ctx.send( f"{len(watching):,} bot{'s' if len(watching) > 1 else ''} are currently tracked for offline status:\n" + ", ".join(watching_list) ) await self.generate_cache() @otherbot_watch_offline.command(name="emoji") async def otherbot_watch_offline_emoji(self, ctx: commands.Context, *, emoji: str = None): """Choose which emoji that will be used for offline messages.""" if not emoji: await self.config.guild(ctx.guild).offline_emoji.set(DEFAULT_OFFLINE_EMOJI) await ctx.send(f"Offline emoji resetted to default: {DEFAULT_OFFLINE_EMOJI}") else: await self.config.guild(ctx.guild).offline_emoji.set(emoji) await ctx.tick() await self.generate_cache() @otherbot_watch_offline.command(name="embed") async def otherbot_watch_offline_embed(self, ctx: commands.Context): """Set wether you want to receive notifications in embed or not.""" current = await self.config.guild(ctx.guild).embed_offline() await self.config.guild(ctx.guild).embed_offline.set(not current) await ctx.send( "I will now send offline notifications in embeds." if not current else "I will no longer send offline notifications in embeds." ) await self.generate_cache() @otherbot_watch.group(name="online") async def otherbot_watch_online(self, ctx: commands.Context): """Manage online notifications.""" @otherbot_watch_online.command(name="add") async def otherbot_watch_online_add(self, ctx: commands.Context, bot: discord.Member): """Add a bot that will be tracked when it comes back online.""" if not bot.bot: return await ctx.send( "You can't track normal users. Please try again with a bot user." ) async with self.config.guild(ctx.guild).online_watching() as watch_list: watch_list.append(bot.id) await ctx.send(f"I will now track {bot.mention} when it goes back online.") await self.generate_cache() @otherbot_watch_online.command(name="remove") async def otherbot_watch_online_remove(self, ctx: commands.Context, bot: discord.Member): """Removes a bot currently tracked.""" if not bot.bot: return await ctx.send( "You can't choose a normal user. Please try again with a bot user." ) async with self.config.guild(ctx.guild).online_watching() as watch_list: try: watch_list.remove(bot.id) await ctx.send(f"Successfully removed {bot.mention} from online tracked bot list.") except ValueError: await ctx.send(f"{bot.mention} is not currently tracked.") await self.generate_cache() @otherbot_watch_online.command(name="list") async def otherbot_watch_online_list(self, ctx: commands.Context): """Lists currently tracked bots.""" watching = await self.config.guild(ctx.guild).online_watching() if not watching: return await ctx.send("There is currently no bots tracked for online status.") watching_list = await self.get_watching(watching, "online_watching", ctx.guild.id) await ctx.send( f"{len(watching):,} bot{'s' if len(watching) > 1 else ''} are currently tracked for online status:\n" + ", ".join(watching_list) ) await self.generate_cache() @otherbot_watch_online.command(name="emoji") async def otherbot_watch_online_emoji(self, ctx: commands.Context, *, emoji: str = None): """Choose which emoji that will be used for online messages.""" if not emoji: await self.config.guild(ctx.guild).online_emoji.set(DEFAULT_ONLINE_EMOJI) await ctx.send(f"Online emoji resetted to default: {DEFAULT_ONLINE_EMOJI}") else: await self.config.guild(ctx.guild).online_emoji.set(emoji) await ctx.tick() await self.generate_cache() @otherbot_watch_online.command(name="embed") async def otherbot_watch_online_embed(self, ctx: commands.Context): """Set wether you want to receive notifications in embed or not.""" current = await self.config.guild(ctx.guild).embed_online() await self.config.guild(ctx.guild).embed_online.set(not current) await ctx.send( "I will now send online notifications in embeds." if not current else "I will no longer send online notifications in embeds." ) await self.generate_cache() @commands.Cog.listener() async def on_presence_update(self, before: discord.Member, after: discord.Member): if after.guild is None or not after.bot: return data = self.otherbot_cache.get(after.guild.id) if data is None: return channel = self.bot.get_channel(data["reporting"]) if not channel: return if not (data["watching"] or data["online_watching"]): return if ( before.status != discord.Status.offline and after.status == discord.Status.offline and (after.id in data["watching"]) ): try: if data["embed_offline"]: em = discord.Embed( color=0x8B0000, description=f"{after.mention} is offline. {data['offline_emoji']}", timestamp=discord.utils.utcnow(), ) if not data["ping"]: await channel.send(embed=em) else: await channel.send( f"<@&{data['ping']}>", embed=em, allowed_mentions=discord.AllowedMentions(roles=True), ) else: if not data["ping"]: await channel.send(f"{after.mention} is offline. {data['offline_emoji']}") else: if discord.version_info.minor < 4: await channel.send( f"<@&{data['ping']}>, {after.mention} is offline. {data['offline_emoji']}" ) else: await channel.send( f"<@&{data['ping']}>, {after.mention} is offline. {data['offline_emoji']}", allowed_mentions=discord.AllowedMentions(roles=True) ) except discord.Forbidden: async with self.config.guild(after.guild).watching() as old_data: old_data.remove(after.id) elif ( before.status == discord.Status.offline and after.status != discord.Status.offline and (after.id in data["online_watching"]) ): try: if data["embed_online"]: em = discord.Embed( color=0x008800, description=f"{after.mention} is back online. {data['online_emoji']}", timestamp=discord.utils.utcnow(), ) if not data["ping"]: await channel.send(embed=em) else: await channel.send( f"<@&{data['ping']}>", embed=em, allowed_mentions=discord.AllowedMentions(roles=True), ) else: if not data["ping"]: await channel.send( f"{after.mention} is back online. {data['online_emoji']}" ) else: if discord.version_info.minor < 4: await channel.send( f"<@&{data['ping']}>, {after.mention} is back online. {data['online_emoji']}" ) else: await channel.send( f"<@&{data['ping']}>, {after.mention} is back online. {data['online_emoji']}", allowed_mentions=discord.AllowedMentions(roles=True), ) except discord.Forbidden: async with self.config.guild(after.guild).online_watching() as old_data: old_data.remove(after.id)