class Player(Cog): def __init__(self, bot: Bot): self.bot = bot self.config = SubRedis(bot.db, "player") self.errorlog = bot.errorlog self.sessions = dict() self.bot.loop.create_task(self._init_all_sessions()) self.bot.loop.create_task(self.cog_reload_cronjob(24 * 60 * 60)) """ ############################################## Setup, Session Initialization, And Breakdown ############################################## """ def get_session(self, guild: Guild) -> Optional[Session]: return self.sessions.get(guild) async def init_session(self, guild: Guild, voice: VoiceChannel, log: TextChannel = None, run_forever: bool = True, **session_config): session = Session(self.bot, self.config, self, voice, log=log, run_forever=run_forever, **session_config) # Add session to sessions and start voice self.sessions[guild] = session await session.session_task() # When voice has ended, disconnect and remove session await session.voice.disconnect() self.sessions.pop(guild) async def _init_all_sessions(self): """Read configs from db and init all sessions""" # Cannot start sessions before bot is logged in and ready await self.bot.wait_until_ready() for init_session_config in self.config.scan_iter("sessions*"): session_config = self.config.hgetall(init_session_config) guild = self.bot.get_guild(int(session_config.pop("guild_id"))) voice = self.bot.get_channel(int(session_config.pop("voice"))) l_id = session_config.pop("log", None) if l_id: log = self.bot.get_channel(int(l_id)) else: log = None self.bot.loop.create_task( self.init_session(guild, voice, log=log, run_forever=True, **session_config)) async def cog_reload_cronjob(self, secs: int): """Async background task added in `__init__` to reload cog after `secs` seconds""" await sleep(secs) self.bot.remove_cog("Player") # Small delay to avoid race condition await sleep(2) self.bot.add_cog(Player(self.bot)) def cog_unload(self): """Stop voice on all sessions to cleanly leave session loop and disconnect voice""" for session in self.sessions.values(): session.stop() """ ################## Requesting Songs ################## """ @group(name="request", aliases=["play"], invoke_without_command=True, enabled=True) @check(user_is_in_voice_channel) @check(user_has_required_permissions) async def request(self, ctx: Context, *, request): """Adds a YouTube video to the requests queue. request: YouTube search query. """ if isinstance(request, str): try: request = YouTubeTrack(request, self.config, requester=ctx.author) except Exception as error: await self.bot.errorlog.send(error, ctx) raise CommandError( f"An error occurred trying to load YouTubeTrack `{request}`" ) session = self.get_session(ctx.guild) if session is None: session = self.sessions[ctx.guild] = Session( self.bot, self.config, self, ctx.author.voice.channel) await ctx.send(**request.request_message) session.queue.add_request(request) @request.command(name="mp3") async def request_mp3(self, ctx: Context, *, request): """Adds a local MP3 file to the requests queue. request: Local track search query. """ try: request = MP3Track(request, config=self.config) except Exception as error: await self.bot.errorlog.send(error, ctx) raise CommandError( f"An error occurred trying to load MP3Track `{request}`") await ctx.invoke(self.request, request=request) @request.command(name="youtube") async def request_youtube(self, ctx: Context, *, request): """Adds a YouTube video to the requests queue. request: YouTube search query. """ try: request = YouTubeTrack(request, self.config, requester=ctx.author) except Exception as error: await self.bot.errorlog.send(error, ctx) raise CommandError( f"An error occurred trying to load YouTubeTrack `{request}`") await ctx.invoke(self.request, request=request) """ ################ Queue Commands ################ """ @command(name="skip") @check(session_is_running) @check(user_is_listening) async def skip(self, ctx: Context): """Skips the currently playing track.""" session = self.get_session(ctx.guild) if ctx.author in session.skip_requests: raise CommandError("You have already requested to skip.") session.skip_requests.append(ctx.author) skips_needed = len(list(session.listeners)) // 2 + 1 if len(session.skip_requests) >= skips_needed: session.voice.stop() else: em = Embed(colour=Colour.dark_green(), title="Skip video", description=f"You currently need " f"**{skips_needed - len(session.skip_requests)}** " f"more votes to skip this track.") await ctx.send(embed=em) @command(name='repeat') @check(session_is_running) @check(user_is_listening) async def repeat(self, ctx: Context): """Repeats the currently playing track.""" session = self.get_session(ctx.guild) if ctx.author in session.repeat_requests: raise CommandError('You have already requested to repeat.') session.repeat_requests.append(ctx.author) repeats_needed = len(list(session.listeners)) // 2 + 1 if len(session.repeat_requests) >= repeats_needed: session.queue.add_request(session.current_track, at_start=True) else: em = Embed(colour=Colour.dark_green(), title='Repeat track', description=f'You currently need ' f'**{repeats_needed - len(session.repeat_requests)}** ' f'more votes to repeat this track.') await ctx.send(embed=em) @command(name='playing', aliases=['now']) @check(session_is_running) async def playing(self, ctx: Context): """Retrieves information on the currently playing track.""" session = self.get_session(ctx.guild) play_time = session.current_track.play_time track_length = session.current_track.length play_time_str = str(timedelta(seconds=play_time)) length_str = str(timedelta(seconds=track_length)) seek_length = 50 seek_distance = round(seek_length * play_time / track_length) message = session.current_track.playing_message message['embed'].add_field( name=f'{play_time_str} / {length_str}', value= f'`{"-" * seek_distance}|{"-" * (seek_length - seek_distance)}`', inline=False) await ctx.send(**message) @command(name="queue", aliases=["upcoming"]) @check(session_is_running) async def queue(self, ctx: Context): session = self.get_session(ctx.guild) em = Embed(colour=Colour.dark_green(), title="Upcoming requests") for index, track in enumerate(session.queue.requests[:10], 1): em.add_field(name=f"{index} - Requested by {track.requester}", value=track.information) if not em.fields: em.description = "There are currently no requests" await ctx.send(embed=em) """ ################ Admin Commands ################ """ @sudo() @command(name="stop") async def stop(self, ctx: Context): session = self.get_session(ctx.guild) if session: session.stop() @sudo() @command(name="start") async def start(self, ctx: Context): session = self.get_session(ctx.guild) if session: session.stop() await sleep(0.5) session_config = self.config.hgetall(f"sessions:{ctx.guild.id}") if session_config: guild = self.bot.get_guild(int(session_config.pop("guild_id"))) voice = self.bot.get_channel(int(session_config.pop("voice"))) l_id = session_config.pop("log", None) if l_id: log = self.bot.get_channel(int(l_id)) else: log = None self.bot.loop.create_task( self.init_session(guild, voice, log=log, run_forever=True, **session_config)) else: raise CommandError(f"Player not configured for {ctx.guild.name}") """ ######## Events ######## """ @Cog.listener() async def on_voice_state_update(self, member: Member, _: VoiceState, after: VoiceState): session = self.get_session(member.guild) if session is not None: if after is None and member in session.skip_requests: session.skip_requests.remove(member) if session.voice is not None: session.check_listeners()
class ModLogs(Cog): def __init__(self, bot: Bot): self.bot = bot self.config = SubRedis(bot.db, "modlog") self.errorlog = bot.errorlog # init a local cache of logged Guilds and their configs cache = dict() for key in self.config.scan_iter("guilds*"): *_, guild_id = key.split(":") try: cache[int(guild_id)] = self.config.hgetall(key) except TypeError: # Guild ID not found self.config.delete(key) self._config_cache = cache @property def active_guilds(self) -> List[int]: # TODO: Use this return list(self._config_cache.keys()) def _is_tracked(self, guild: Guild, priority_event: bool): """Perform a simple check before running each event so that we don't waste time trying to log""" if not guild: # DMs return False elif guild.id not in self._config_cache.keys(): return False elif priority_event and self._config_cache[guild.id].get( "priority_modlog") is None: return False elif not priority_event and self._config_cache[guild.id].get( "default_modlog") is None: return False else: return True def _create_guild_config(self, guild: Guild): config = { "priority_modlog": "None", "default_modlog": "None", } self.config.hmset(f"guilds:{guild.id}", config) self._config_cache[int(guild.id)] = config return config def get_guild_config(self, guild: Guild): """ Get the guild's config, or create it if it doesn't exist. Expected format should be: { "priority_modlog": "id", "default_modlog": "id", } either modlog can be `None`, which just results in the event being discarded. :param guild: The tracked guild :return: guild config dict, or None if it doesn't exist """ try: return self._config_cache.get(guild.id) except KeyError: # Let's just build it up anyway return self.config.hgetall(f"guilds:{guild.id}") def em_base(self, user: Union[Member, User], log_title: str, color: int) -> Embed: """Do basic formatting on the embed""" em = Embed(description=f"*{log_title}*", color=color) user_repr = f"{user.name}#{user.discriminator} (ID: {user.id})" em.set_author(name=user_repr, icon_url=user.avatar_url) em.set_footer(text=self._get_timestamp()) return em @staticmethod def _get_timestamp() -> str: """Returns a formatted timestamp based on server region""" dt = timezone("UTC").localize( datetime.utcnow()).strftime("%b. %d, %Y#%H:%M UTC") date, time = dt.split("#") return f"Event Timestamp: 📅 {date} 🕒 {time}" async def log_event(self, embed: Embed, guild: Guild, priority: bool = False, **kwargs) -> None: """Have to use this backwards-ass method because it throws http exceptions.""" guild_config = self.get_guild_config(guild) if priority: priority_modlog = int(guild_config.get("priority_modlog", 0)) dest = self.bot.get_channel(priority_modlog) else: default_modlog = int(guild_config.get("default_modlog", 0)) dest = self.bot.get_channel(default_modlog) if not dest: return try: for i, page in enumerate(embed.split()): if i: await sleep(0.1) await dest.send(embed=page, **kwargs) except HTTPException as error: await self.errorlog.send(error) async def _get_last_audit_action( self, guild: Guild, action: int, member: Union[Member, User] ) -> Tuple[bool, bool, Optional[User], Optional[str]]: """Find the first Audit Log entry matching the action type. Will only search up to 10 log entries and only up to 5 seconds ago. Returns Tuple bool: If log entry was found bool: If exception is encountered (to adjust embed message) Optional[User]: The moderator that used moderation action or None Optional[str]: The reason given for moderation action or None""" # Allow time so audit logs will be available await sleep(0.5) # Only search last 10 seconds of audit logs timeframe = datetime.utcnow() - timedelta(seconds=10.0) try: # Only search last 10 audit log entries # Action should be at the top of the stack for log_entry in await guild.audit_logs( action=action, limit=10, oldest_first=False).flatten(): # after kwarg of Guild.audit_logs does not appear to work # Manually compare datetimes if log_entry.target.id == member.id and log_entry.created_at > timeframe: # Get mod and reason # Should always get mod # Reason is optional mod = getattr(log_entry, "user", None) reason = getattr(log_entry, "reason", None) return True, False, mod, reason # Could not find audit log entry # member_remove was voluntary leave else: return False, False, None, None # Do not have access to audit logs except Forbidden as error: print(error) return False, True, None, None # Catch any unknown errors and log them # We need this method to return so event still logs except Exception as error: await self.errorlog.send(error) return False, True, None, None """ ################### Registered Events ################### """ @Cog.listener(name="on_member_ban") async def on_member_ban(self, guild: Guild, user: Union[Member, User], *args): """Event called when a user is banned. User does not need to currently be a member to be banned.""" if not self._is_tracked(guild, EventPriority.ban): return # Event is sometimes called with 3 arguments # Capture occurrence await self.errorlog.send( Exception(f"Additional arguments sent to `on_member_ban`: {args}")) em = self.em_base(user, f"User {user.mention} ({user.name}) was banned", EventColors.ban.value) # Attempt to retrieve unban reason and mod that unbanned from Audit Log found, errored, mod, reason = await self._get_last_audit_action( guild, AuditLogAction.ban, user) # Audit log action found # Add details if found and not errored: em.add_field( name="Banned By", value=f"{mod.mention}\n({mod.name}#{mod.discriminator})") em.add_field( name="Reason", value=reason if reason is not None else "No reason given") # Cannot access audit log or HTTP error prevented access elif errored and not found: em.add_field(name="Banned By", value="Unknown\nAudit Log inaccessible") em.add_field(name="Reason", value="Irretrievable\nAudit Log inaccessible") # No audit log entry found for ban else: em.add_field(name="Banned By", value="Unknown\nAudit Log missing data") em.add_field( name="Reason", value="Irretrievable\nAudit Log missing data or no reason given" ) # If banned user was a member of the server, capture roles if isinstance(user, Member): roles = "\n".join([ f"{role.mention} ({role.name})" for role in sorted(user.roles, reverse=True) if role.name != "@everyone" ]) em.add_field(name="Roles", value=roles if roles else "User had no roles") await self.log_event(em, guild, priority=EventPriority.ban) @Cog.listener(name="on_member_unban") async def on_member_unban(self, guild: Guild, user: User, *args): """Event called when a user is unbanned""" if not self._is_tracked(guild, EventPriority.unban): return # Event is sometimes called with 3 arguments # Capture occurrence await self.errorlog.send( Exception( f"Additional arguments sent to `on_member_unban`: {args}")) em = self.em_base(user, f"User {user.mention} ({user.name}) was unbanned", EventColors.unban.value) # Attempt to retrieve unban reason and mod that unbanned from Audit Log found, errored, mod, reason = await self._get_last_audit_action( guild, AuditLogAction.unban, user) # Audit log action found # Add details if found and not errored: em.add_field( name="Unbanned By", value=f"{mod.mention}\n({mod.name}#{mod.discriminator})") em.add_field( name="Reason", value=reason if reason is not None else "No reason given") # Cannot access audit log or HTTP error prevented access elif errored and not found: em.add_field(name="Unbanned By", value="Unknown\nAudit Log inaccessible") em.add_field(name="Reason", value="Irretrievable\nAudit Log inaccessible") # No audit log entry found for ban else: em.add_field(name="Unbanned By", value="Unknown\nAudit Log missing data") em.add_field( name="Reason", value="Irretrievable\nAudit Log missing data or no reason given" ) await self.log_event(em, guild, priority=EventPriority.unban) @Cog.listener(name="on_member_join") async def on_member_join(self, member: Member): """Event called when a member joins the guild""" if not self._is_tracked(member.guild, EventPriority.join): return em = self.em_base(member, f"User {member.mention} ({member.name}) joined", EventColors.join.value) em.add_field(name="Account Creation Timestamp", value=self._get_timestamp()) await self.log_event(em, member.guild, priority=EventPriority.join) @Cog.listener(name="on_member_remove") async def on_member_remove(self, member: Member): """Event called when a member is removed from the guild This event will be called if the member leaves, is kicked, or is banned""" if not self._is_tracked(member.guild, EventPriority.leave): return # Stop if ban. Will be handled in on_member_ban found, *_ = await self._get_last_audit_action(member.guild, AuditLogAction.ban, member) if found: return # Attempt to retrieve kic reason and mod that kicked from Audit Log found, errored, mod, reason = await self._get_last_audit_action( member.guild, AuditLogAction.kick, member) # Kick found in audit log if found and not errored: leave_type = EventPriority.kick em = self.em_base( member, f"User {member.mention} ({member.name}) was kicked", EventColors.kick.value) em.add_field( name="Kicked By", value=f"{mod.mention}\n({mod.name}#{mod.discriminator})") em.add_field(name="Reason", value=reason if reason else "No reason given") # Cannot access audit log or HTTP error prevented access elif errored and not found: print("errored and not found") leave_type = EventPriority.kick em = self.em_base(member, f"User {member.name} may have been kicked", EventColors.kick.value) em.description = f"{em.description}\n\nAudit Log inaccessible\n" \ f"Unable to determine if member remove was kick or leave" em.add_field(name="Kicked By", value="Unknown\nAudit Log inaccessible") em.add_field(name="Reason", value="Irretrievable\nAudit Log inaccessible") # Successfully accessed audit log and found no kick # Presume voluntary leave else: leave_type = EventPriority.leave em = self.em_base(member, f"User {member.mention} ({member.name}) left", EventColors.kick.value) roles = "\n".join([ f"{role.mention} ({role.name})" for role in sorted(member.roles, reverse=True) if role.name != "@everyone" ]) em.add_field(name="Roles", value=roles if roles else "User had no roles") await self.log_event(em, member.guild, priority=leave_type) @Cog.listener(name="on_message_delete") async def on_message_delete(self, msg: Message): """Event called when a message is deleted""" if not self._is_tracked(msg.guild, EventPriority.delete): return modlog_channels = [ int(channel_id) for channel_id in self.get_guild_config(msg.guild).values() ] # If message deleted from modlog, record event with header only if msg.channel.id in modlog_channels: description = f"\n\n{msg.embeds[0].description}" if msg.embeds else "" description = escape_markdown( description.replace("Modlog message deleted\n\n", "")) em = self.em_base(msg.author, f"Modlog message deleted{description}", EventColors.delete.value) return await self.log_event(em, msg.guild, EventPriority.delete) # Otherwise, ignore bot's deleted embed-only (help pages, et.) messages elif msg.author.id == self.bot.user.id and not msg.content: return em = self.em_base( msg.author, f"Message by {msg.author.mention} ({msg.author.name}) deleted", EventColors.delete.value) em.description = f"{em.description}\n\nChannel: {msg.channel.mention} ({msg.channel.name})" if msg.content: chunks = [ msg.content[i:i + 1024] for i in range(0, len(msg.content), 1024) ] for i, chunk in enumerate(chunks): em.add_field(name=f"🗑 Content [{i + 1}/{len(chunks)}]", value=chunk) else: em.add_field(name="🗑 Content [0/0]", value="Message had no content") # Try to re-download attached images if possible. The proxy url doesn't 404 immediately unlike the # regular URL, so it may be possible to download from it before it goes down as well. reupload = None if msg.attachments: temp_image = BytesIO() attachment = msg.attachments[0] if attachment.size > 5000000: # caching is important and all, but this will just cause more harm than good return try: await download_image(msg.attachments[0].proxy_url, temp_image) reupload = File(temp_image, filename="reupload.{}".format( attachment.filename)) em.description = f"{em.description}\n\n**Attachment Included Above**" except Exception as error: await self.errorlog.send(error) reupload = None em.description = f"{em.description}\n\n**Attachment Reupload Failed (See Error Log)**" await self.log_event(em, msg.guild, priority=EventPriority.delete, file=reupload) @Cog.listener(name="on_bulk_message_delete") async def on_bulk_message_delete(self, msgs: List[Message]): """Event called when messages are bulk deleted""" # Bulk delete event triggered with no messages or messages not found in cache if not msgs: return if not self._is_tracked(msgs[0].guild, EventPriority.delete): return # modlog_channels = [int(channel_id) for channel_id in self.get_guild_config(msgs[0].guild).values()] # # # If messages deleted from modlog, record event with headers only # if msgs[0].channel.id in modlog_channels: # # description = f"\n\n{msg.embeds[0].description}" if msg.embeds else "" # description = escape_markdown(description.replace("Modlog message deleted\n\n", "")) # # em = self.em_base( # msg.author, # f"Modlog messages deleted{description}", # EventColors.delete.value # ) # # return await self.log_event(em, msg.guild, EventPriority.delete) em = self.em_base(self.bot.user, f"Messages bulk deleted", EventColors.bulk_delete.value) em.description = f"{em.description}\n\nChannel: {msgs[0].channel.mention} ({msgs[0].channel.name})" for i, msg in enumerate(msgs): content = f"__Content:__ {escape_markdown(msg.content)}" if msg.content else "Message had no content" if msg.attachments: content = f"{content}\n__Attachments:__ {', '.join([file.filename for file in msg.attachments])}" if msg.embeds: embed = f"__Embed Title:__ {escape_markdown(msg.embeds[0].title)}\n" \ f"__Embed Description:__ {escape_markdown(msg.embeds[0].description)}" content = f"{content}\n{embed}" content = [ content[i:1024 + i] for i in range(0, len(content), 1024) ] for page in content: em.add_field( name= f"{msg.author.name}#{msg.author.discriminator} [{i + 1}/{len(msgs)}]", value=page) await self.log_event(em, msgs[0].guild, EventPriority.delete) # msgs_raw = list() # # for msg in msgs: # msgs_raw.append( # f"**__{msg.author.name}#{msg.author.discriminator}__** ({msg.author.id})\n" # f"{escape_markdown(msg.content)}" # ) # # msg_stream = "\n".join(msgs_raw).split("\n") # # field_values = list() # current = "" # # for line in msg_stream: # # if len(current) + len(line) < 1024: # current = f"{current}\n{line}" # # else: # field_values.append(current) # current = line # # else: # field_values.append(current) # # total = len(field_values) # field_groups = [field_values[i:25 + i] for i in range(0, len(field_values), 25)] # # for n, field_group in enumerate(field_groups): # page = em.copy() # if len(field_groups) > 1: # if n < 1: # page.set_footer("") # else: # page.title = "" # page.description = "" # page.set_author(name="", url="", icon_url="") # # for i, msg_raw in enumerate(field_group): # page.add_field( # name=f"🗑 Messages [{(n + 1) * (i + 1)}/{total}]", # value=msg_raw # ) # # await self.log_event(page, msgs[0].guild, EventPriority.delete) @Cog.listener(name="on_message_edit") async def on_message_edit(self, before: Message, after: Message): """Event called when a message is edited""" if not self._is_tracked(before.guild, EventPriority.edit): return if before.author.id == self.bot.user.id: return if before.content == after.content or isinstance( before.channel, DMChannel): return em = self.em_base( before.author, f"Message by {before.author.mention} ({before.author.name}) edited", EventColors.edit.value) em.description = f"{em.description}\n\nChannel: {before.channel.mention} ({before.channel.name})" if before.content: chunks = [ before.content[i:i + 1024] for i in range(0, len(before.content), 1024) ] for i, chunk in enumerate(chunks): em.add_field(name=f"🗑 Before [{i + 1}/{len(chunks)}]", value=chunk, inline=False) else: em.add_field(name="🗑 Before [0/0]", value="Message had no content", inline=False) if after.content: chunks = [ after.content[i:i + 1024] for i in range(0, len(after.content), 1024) ] for i, chunk in enumerate(chunks): em.add_field(name=f"💬 After [{i + 1}/{len(chunks)}]", value=chunk, inline=False) await self.log_event(em, before.guild, priority=EventPriority.edit) @Cog.listener(name="on_member_update") async def on_member_update(self, before: Member, after: Member): """Event called when a user's member profile is changed""" if not self._is_tracked(before.guild, EventPriority.update): return if before.name != after.name or before.discriminator != after.discriminator: em = self.em_base( after, f"Member {before.mention} ({before.name}#{before.discriminator}) " f"changed their name to {after.name}#{after.discriminator}", EventColors.name_change.value) await self.log_event(em, before.guild, priority=EventPriority.update) if before.roles != after.roles: added, removed = None, None for role in before.roles: if role not in after.roles: removed = f"{role.mention}\n({role.name})" for role in after.roles: if role not in before.roles: added = f"{role.mention}\n({role.name})" found, errored, mod, _ = await self._get_last_audit_action( after.guild, AuditLogAction.member_role_update, after) if added: em = self.em_base( after, f"Member {after.mention} ({after.name}) roles changed", EventColors.role_added.value) em.add_field(name="Role Added", value=added) if found: em.add_field(name="Mod Responsible", value=f"{mod.mention}\n({mod.name})") await self.log_event(em, before.guild, priority=EventPriority.update) if removed: em = self.em_base( after, f"Member {after.mention} ({after.name}) roles changed", EventColors.role_removed.value) em.add_field(name="Role Removed", value=removed) if found: em.add_field(name="Mod Responsible", value=f"{mod.mention}\n({mod.name})") await self.log_event(em, before.guild, priority=EventPriority.update) if before.nick != after.nick: if after.nick is None: em = self.em_base( after, f"Member {before.mention} ({before.name}) reset their nickname", EventColors.nickname_change.value) else: em = self.em_base( after, f"Member {before.mention} ({before.name}) changed their nickname " f"from {before.nick} to {after.nick}", EventColors.nickname_change.value) await self.log_event(em, after.guild, priority=EventPriority.update) """ ########## Commands ########## """ @sudo() @group(name="modlog", invoke_without_command=True) async def modlog( self, ctx: Context): # TODO: List enabled guilds with their channels pass @sudo() @modlog.command(name="enable") async def enable(self, ctx: Context, *, guild: int = None): """Enable logging on a Guild You must also set default and/or priority log channels with `[p]modlog set (default/priority)`""" if guild is None: guild = ctx.guild else: guild = self.bot.get_guild(guild) if not guild: return await ctx.message.add_reaction("⚠") self._create_guild_config(guild) await ctx.message.add_reaction("✅") @sudo() @modlog.command(name="disable") async def disable(self, ctx: Context, guild: int = None): """Disable logging on a Guild Guild and its config will be removed from the database""" if guild is None: guild = ctx.guild else: guild = self.bot.get_guild(guild) if not guild: return await ctx.message.add_reaction("⚠") if guild.id not in self.active_guilds: return await ctx.message.add_reaction("⚠") self._config_cache.pop(guild.id) self.config.delete(f"guilds:{guild.id}") await ctx.message.add_reaction("✅") @sudo() @modlog.group(name="set", invoke_without_command=True) async def _set(self, ctx: Context): # TODO: Show guilds and configs? pass @sudo() @_set.command(name="default") async def default(self, ctx: Context, *, guild: int = None, channel: int = None): """Set modlog Channel for "default" messages `guild` must be a tracked Guild `channel` does not necessarily need to be a Channel in `guild`""" if not guild: guild = ctx.guild else: guild = self.bot.get_guild(guild) if not guild: return await ctx.message.add_reaction("⚠") if guild.id not in self.active_guilds: return await ctx.message.add_reaction("⚠") if not channel: channel = ctx.channel else: channel = self.bot.get_channel(channel) if not channel: return await ctx.message.add_reaction("⚠") config = self.get_guild_config(guild) config["default_modlog"] = str(channel.id) self.config.hmset(f"guilds:{guild.id}", config) self._config_cache[guild.id] = config await ctx.message.add_reaction("✅") @sudo() @_set.command(name="priority") async def priority(self, ctx: Context, *, guild: int = None, channel: int = None): """Set modlog channel for "priority" messages `guild` must be a tracked Guild `channel` does not necessarily need to be a Channel in `guild`""" if not guild: guild = ctx.guild else: guild = self.bot.get_guild(guild) if not guild: return await ctx.message.add_reaction("⚠") if guild.id not in self.active_guilds: return await ctx.message.add_reaction("⚠") if not channel: channel = ctx.channel else: channel = self.bot.get_channel(channel) if not channel: return await ctx.message.add_reaction("⚠") config = self.get_guild_config(guild) config["priority_modlog"] = str(channel.id) self.config.hmset(f"guilds:{guild.id}", config) self._config_cache[guild.id] = config await ctx.message.add_reaction("✅")
if prefix_config["when_mentioned"]: prefix.extend(when_mentioned(client, msg)) # If in a guild, check for guild-specific prefix if isinstance(msg.channel, TextChannel): guild_prefix = config.hget("prefix:guild", msg.channel.guild.id) if guild_prefix: prefix.append(guild_prefix) return prefix bot = Bot(db=db, app_name=APP_NAME, command_prefix=command_prefix, **config.hgetall("instance")) @bot.event async def on_ready(): """Coroutine called when bot is logged in and ready to receive commands""" # "Loading" status message loading = "around, setting up shop." await bot.change_presence(activity=Activity(name=loading, type=0)) # Bot account metadata such as bot user ID and owner identity bot.app_info = await bot.application_info() bot.owner = bot.get_user(bot.app_info.owner.id) # Add the ErrorLog object if the channel is specified