async def tempban( self, ctx: Context, target: Member, length: int, span: str, *, reason: str = None ): """Temporarily ban a member from the server. For timing, plural and non-plural spans are accepted (Day, days, minutes, etc). Use "max" as the span for pseudo-permanence (10 years). Ban member permission required. """ sid = str(ctx.guild.id) if sid not in self.tempban_db: self.tempban_db[sid] = {} # Get the current UTC time, a future time from time_parser, and the difference now = datetime.now(tz=timezone.utc) future = time_parser(span, length, now) length = future - now embed = await embed_builder("Temporarily Banned", target, reason, length) await target.send(embed=embed) await target.ban(reason=reason, delete_message_days=0) self.tempban_db[sid][target.id] = { "issued_by": str(ctx.author.id), "reason": reason, "expires": str(future.timestamp()), } update_db(self.sql_db, self.tempban_db, "temp_bans") tag = f"{target.name}#{target.discriminator}" await ctx.send(f":white_check_mark: Tempbanned {tag} for {reason}") await self.log_to_channel(ctx, target, reason)
async def cmd_plugins_reload(self, ctx: Context, name: str): """Reload plugin (Cog). Do not include file extension. Botmaster required. """ if name not in self.bot.plugins: await ctx.send(f":anger: Plugin {name}.py is not loaded.") else: try: self.copy_plugin_if_needed(name) self.bot.unload_extension(f"plugins.{name}") self.bot.plugins.remove(name) update_db(self.bot.db, self.bot.plugins, "plugins") await ctx.send( f":white_check_mark: Plugin {name}.py successfully unloaded." ) self.bot.load_extension(f"plugins.{name}") self.bot.plugins.append(name) update_db(self.bot.db, self.bot.plugins, "plugins") await ctx.send( f":white_check_mark: Plugin {name}.py successfully loaded." ) except Exception as e: exc = f"{type(e).__name__}, {e}" await ctx.send(f":anger: Error reloading {name}.py:\n```py\n{exc}\n```")
async def admin_log(self, ctx: Context, enabled: bool, channel: TextChannel = None): """Enable/disable to-channel logging and set the log channel. MUST HAVE SERVER ADMINISTRATOR PERMISSION """ sid = str(ctx.guild.id) if sid not in self.db: self.db[sid] = {} self.db[sid]["log"] = enabled if channel is not None: self.db[sid]["log_channel"] = str(channel.id) else: if "log_channel" not in self.db[sid]: self.db[sid]["log_channel"] = str(ctx.message.channel.id) channel = ctx.message.channel update_db(self.sql_db, self.db, "admin") embed = Embed(title="Log Settings", color=0xFF0000) embed.add_field(name="Enabled", value=str(enabled)) embed.add_field(name="Log Channel", value=channel.mention) await ctx.send(embed=embed)
async def role_admin_add(self, ctx: Context, role_get: Role, *, description: str): """Add or update a role on the assignable roles list. Server administrator permission required. """ sid = str(ctx.guild.id) rid = str(role_get.id) name = role_get.name.lower() if sid not in self.db: self.db[sid] = {"roles": {}} elif "roles" not in self.db[sid]: self.db[sid]["roles"] = {} elif name in self.db[sid]["roles"]: if self.db[sid]["roles"][name]["id"] != rid: await ctx.send( ":anger: There is already a role with the same name in the " "assignable roles list.") else: await ctx.send(":anger: That roles ia already assignable.") return try: self.db[sid]["roles"][name] = { "id": rid, "description": description } await ctx.send( f":white_check_mark: Added {name} to assignable roles.") update_db(self.sql_db, self.db, "servers") except Exception as e: await ctx.send(f":anger: Error adding role: {e}")
async def on_raw_message_delete(self, payload): try: del self.db[str(payload.guild_id)]["reacts"][str( payload.message_id)] update_db(self.sql_db, self.db, "servers") except KeyError: pass
async def unmute(self, ctx: Context, target: Member): """Unmute a member early. Kick member permission required. """ sid = str(ctx.guild.id) uid = str(target.id) mute_role = None try: mute_role = ctx.guild.get_role(int(self.db[sid]["mute_role"])) except KeyError: await ctx.send(":anger: This server has no mute role set.") return if sid not in self.mute_db: await ctx.send(":anger: This server has no mutes.") return if uid not in self.mute_db[sid]: await ctx.send(":anger: This member is not muted.") return await target.send(f":speaking_head: You have been unmuted in {ctx.guild.name}.") await ctx.send(f":speaking_head: Unmuted {target.name}.") await target.remove_roles(mute_role) del self.mute_db[sid][uid] update_db(self.sql_db, self.mute_db, "mutes")
async def groups_create(self, ctx: Context, name: str, *, description: str): """Create a group for you and your friends! Name must NOT include spaces and is CaSe SeNsItIvE! """ if len(name) > 48: await ctx.send( ":anger: Please use a name shorter than 48 characters.") return sid = str(ctx.guild.id) if sid not in self.db: self.db[sid] = {} # Try to make a role, text, and voice channel for the group try: role = await ctx.guild.create_role(name=name, reason="Groups plugin") ow = { ctx.guild.default_role: PermissionOverwrite(read_messages=False), role: PermissionOverwrite(read_messages=True), } category = await ctx.guild.create_category(name=name, reason="Groups plugin", overwrites=ow) text = await ctx.guild.create_text_channel( name=name.lower(), reason="Groups plugin", category=category, topic=description, ) voice = await ctx.guild.create_voice_channel( name=name, reason="Groups plugin", category=category) self.db[sid][name] = { "info": { "leader": str(ctx.author.id), "description": description, "category": str(category.id), "text_channel": str(text.id), "voice_channel": str(voice.id), "role": str(role.id), } } update_db(self.sql_db, self.db, "servers") await ctx.author.add_roles(role, reason="Group created.") except Exception as e: await ctx.send(f":anger: Something went wrong: `{e}`") return await ctx.send(":white_check_mark: Group created!") await text.send( f"Welcome to your group {ctx.author.mention}! Try the `group invite` command!" )
async def on_guild_join(self, guild: Guild): sid = str(guild.id) self.bot.log.info(f"[JOIN] {guild.name}") if sid not in self.bot.servers: self.bot.servers[sid] = {} update_db(self.bot.db, self.bot.servers, "servers")
async def on_guild_remove(self, guild: Guild): sid = str(guild.id) self.bot.log.info(f"[LEAVE] {guild.name}") if sid in self.bot.servers: self.bot.servers.pop(sid) update_db(self.bot.db, self.bot.servers, "servers")
async def warn( self, ctx: Context, target: Member, length: int, span: str, *, reason: str ): """Warn a member. For timing, plural and non-plural spans are accepted (Day, days, minutes, etc). Use "max" as the span for pseudo-permanence (10 years). Kick member permission required. """ sid = str(ctx.guild.id) uid = str(target.id) warn_count = 1 if sid not in self.warn_db: self.warn_db[sid] = {} if uid in self.warn_db[sid]: for _ in self.warn_db[sid][uid]: warn_count += 1 # Get the current UTC time, a future time from time_parser, and the difference now = datetime.now(tz=timezone.utc) future = time_parser(span, length, now) length = future - now embed = await embed_builder("Warned", target, reason, length) await target.send(embed=embed) await target.send(f":warning: This is warning #{warn_count}.") await ctx.send( f":warning: Warning {warn_count} issued to {target.name} for {reason}" ) if uid not in self.warn_db[sid]: self.warn_db[sid][uid] = {} def db_check(count: int) -> int: if str(count) in self.warn_db[sid][uid]: count += 1 return db_check(count) else: return count warn_count = db_check(warn_count) warning = { "issued_by": str(ctx.author.id), "reason": reason, "expires": str(future.timestamp()), } self.warn_db[sid][uid][str(warn_count)] = warning update_db(self.sql_db, self.warn_db, "warns") await self.log_to_channel(ctx, target, reason)
async def cmd_plugins_disable(self, ctx: Context, name: str): """Disable a loaded plugin (Cog) on the current server. Server administrator permission required. """ if name not in self.bot.plugins: await ctx.send(f":anger: No plugin {name} is loaded.") return else: sid = str(ctx.guild.id) self.bot.servers[sid][name] = False update_db(self.bot.db, self.bot.servers, "servers") await ctx.send(f":white_check_mark: Plugin {name} disabled on your server.")
async def cmd_logs_deletes(self, ctx: Context, enabled: bool): """Set logging of deleted messages to the server's log channel. MUST HAVE SERVER ADMINISTRATOR PERMISSION """ sid = str(ctx.guild.id) if sid not in self.bot.servers: self.bot.servers[sid] = {} self.bot.servers[sid]["log_deletes"] = enabled update_db(self.bot.db, self.bot.servers, "servers") await ctx.send( f":white_check_mark: Logging message deletes set to {enabled}.")
async def admin_role(self, ctx: Context, role: Role): """Set the mute role for the server. MUST HAVE SERVER ADMINISTRATOR PERMISSION """ sid = str(ctx.guild.id) if sid not in self.db: self.db[sid] = {} self.db[sid]["mute_role"] = str(role.id) update_db(self.sql_db, self.db, "admin") await ctx.send(f":white_check_mark: Mute role set to: {role.name}.")
async def cmd_logs_channel(self, ctx: Context, channel: TextChannel): """Set the server's message logging channel. MUST HAVE SERVER ADMINISTRATOR PERMISSION """ sid = str(ctx.guild.id) if sid not in self.bot.servers: self.bot.servers[sid] = {} self.bot.servers[sid]["log_channel"] = str(channel.id) update_db(self.bot.db, self.bot.servers, "servers") await ctx.send( f":white_check_mark: Logging channel set to {channel.mention}.")
async def script_remove(self, ctx: Context, prefix: str): """Remove a custom response. Manage messages permission required. """ sid = str(ctx.guild.id) try: del self.db[sid]["complex"][prefix] update_db(self.sql_db, self.db, "servers") await ctx.send( f":white_check_mark: Script response `{prefix}` removed.") except KeyError: await ctx.send(f":anger: There is no script response `{prefix}` " "registered on this server.")
async def text_remove(self, ctx: Context, name: str): """Remove a custom text command. Manage messages permission required. """ sid = str(ctx.guild.id) try: del self.db[sid]["text"][name] update_db(self.sql_db, self.db, "servers") await ctx.send(f":white_check_mark: Command `{name}` removed.") except KeyError: await ctx.send( f":anger: There is no command `{name}` registered on this server." )
async def role_react_add(self, ctx: Context, message: Message, role_get: Role, *, description: str): """Add a new reaction-based role to a message in your server. This will start a very quick interactive process for you to select the reaction. Server administrator permission required. """ sid = str(ctx.guild.id) if sid not in self.db: self.db[sid] = {"reacts": {}} elif "reacts" not in self.db[sid]: self.db[sid]["reacts"] = {} prompt = await ctx.send("React to this message to set your emoji.") try: reaction, _ = await ctx.bot.wait_for( "reaction_add", timeout=20.0, check=lambda r, m: m == ctx.message.author and r.message.id == prompt.id, ) except asyncio.TimeoutError: await ctx.send(":anger: You took too long to react!") return mid = str(message.id) if mid not in self.db[sid]["reacts"]: self.db[sid]["reacts"][mid] = {} # Convert the reaction emoji to a string if needed if isinstance(reaction.emoji, (Emoji, PartialEmoji)): reaction = reaction.emoji.name else: reaction = reaction.emoji self.db[sid]["reacts"][mid][role_get.name] = { "description": description, "id": role_get.id, "reaction": reaction, "channel": message.channel.id, "message": message.id, } update_db(self.sql_db, self.db, "servers") await ctx.send( f":white_check_mark: Role {role_get.name} added with {reaction}.")
async def ghosts(self, ctx: Context, enabled: bool = True): """Set per-server reporting of deleted messages contianing mentions (pings). This causes a 'Ghost' notification on the client of the user who was mentioned. If enabled, the bot will post a message showing all users mentioned. """ sid = str(ctx.guild.id) if sid not in self.bot.servers: self.bot.servers[sid] = {} self.bot.servers[sid]["report_ghosts"] = enabled update_db(self.bot.db, self.bot.servers, "servers") await ctx.send(f":white_check_mark: Ghost reporting set to {enabled}.")
async def group_check(self): if len(self.db) <= 0: return for sid in self.db: if len(self.db[sid]) <= 0: del self.db[sid] continue for group, data in self.db[sid].items(): info = data["info"] guild = self.bot.get_guild(int(sid)) # Get the related channels and roles category = guild.get_channel(int(info["category"])) text_channel = guild.get_channel(int(info["text_channel"])) voice_channel = guild.get_channel(int(info["voice_channel"])) role = guild.get_role(int(info["role"])) # Try to get the latest message from the text channel try: last_message = await text_channel.fetch_message( text_channel.last_message_id) except Exception as e: # No message found, or there was some other error self.bot.log.error(f"[ERROR][GROUPS]\n - {e}") last_message = None try: last_datetime = last_message.created_at.replace( tzinfo=timezone.utc) delta = datetime.now(tz=timezone.utc) - last_datetime since_sent = int(delta.total_seconds()) except Exception as e: self.bot.log.error(f"[ERROR][GROUPS]\n - {e}") since_sent = 1801 if not voice_channel.members and since_sent >= 1800: try: for chan in (text_channel, voice_channel, category, role): await chan.delete( reason="Groups Plugin (Inactivity)") del self.db[sid][group] update_db(self.sql_db, self.db, "servers") except Exception as e: self.bot.log.error(f"[ERROR][GROUPS]:\n - {e}")
async def cmd_plugins_enable(self, ctx: Context, name: str): """Enable a loaded plugin (Cog) on the current server. Server administrator permission required. """ if name not in self.bot.plugins: # There is a distinction between server-loaded and bot-loaded plugins # therefore I do not include the .py extension here purposefully await ctx.send(f":anger: No plugin {name} is loaded.") return else: sid = str(ctx.guild.id) self.bot.servers[sid][name] = True update_db(self.bot.db, self.bot.servers, "servers") await ctx.send(f":white_check_mark: Plugin {name} enabled on your server.")
async def text_create(self, ctx: Context, name: str, *, text: str): """Create or update a new custom text command. Manage messages permission required. """ sid = str(ctx.guild.id) if sid not in self.db: self.db[sid] = {"prefix": "_", "text": {}} elif "text" not in self.db[sid]: self.db[sid]["text"] = {} try: self.db[sid]["text"][name] = text update_db(self.sql_db, self.db, "servers") await ctx.send(f":white_check_mark: Command {name} added!") except Exception as e: await ctx.send(f":anger: Something went wrong: {e}")
async def mute( self, ctx: Context, target: Member, length: int, span: str, *, reason: str ): """Set a member to the mute role. For timing, plural and non-plural spans are accepted (Day, days, minutes, etc). Use "max" as the span for pseudo-permanence (10 years). Kick member permission required. """ sid = str(ctx.guild.id) uid = str(target.id) mute_role = None try: mute_role = ctx.guild.get_role(int(self.db[sid]["mute_role"])) except KeyError: await ctx.send(":anger: Server has no mute role set.") return if sid not in self.mute_db: self.mute_db[sid] = {} # Get the current UTC time, a future time from time_parser, and the difference now = datetime.now(tz=timezone.utc) future = time_parser(span, length, now) length = future - now time = pretty_timedelta(length) embed = await embed_builder("Muted", target, reason, length) await target.send(embed=embed) await ctx.send( f":white_check_mark: {target.name} muted for {reason}, expires in {time}" ) await target.add_roles(mute_role) mute = { "issued_by": str(ctx.author.id), "reason": reason, "expires": str(future.timestamp()), } self.mute_db[sid][uid] = mute update_db(self.sql_db, self.mute_db, "mutes") await self.log_to_channel(ctx, target, reason)
async def script_create(self, ctx: Context, prefix: str, *, text: str): """Create or update a new script response. Manage messages permission required. """ sid = str(ctx.guild.id) if sid not in self.db: self.db[sid] = {"complex": {}} elif "complex" not in self.db[sid]: self.db[sid]["complex"] = {} try: self.db[sid]["complex"][prefix] = text update_db(self.sql_db, self.db, "servers") await ctx.send( f":white_check_mark: Script response {prefix} added!") except Exception as e: await ctx.send(f":anger: Something went wrong: {e}")
async def role_admin_invokes(self, ctx: Context, remove: bool = None): """Manage role commands and confirmation messages being deleted on your server. If DeleteCommands is set to True in the bot's config, this will only affect the confirmation messages. Running the command without arguments will display the current setting. Server administrator permission required. """ sid = str(ctx.guild.id) if sid not in self.db: self.db[sid] = {"remove": False} if remove is not None: self.db[sid]["remove"] = remove update_db(self.sql_db, self.db, "servers") await ctx.send(f"Remove role invokes: `{self.db[sid]['remove']}`")
async def tempban_check(self): ts = datetime.now(tz=timezone.utc).timestamp() if len(self.tempban_db) <= 0: return for sid in self.tempban_db: if len(self.tempban_db[sid]) <= 0: continue for uid in self.tempban_db[sid]: info = self.tempban_db[sid][uid] if ts >= float(info["expires"]): guild = self.bot.get_guild(int(sid)) await guild.unban(self.bot.get_user(uid)) del self.tempban_db[sid][uid] update_db(self.sql_db, self.tempban_db, "temp_bans") self.bot.log.info(f"[ADMIN][TEMPBAN][REMOVE] {uid} in <{guild.name}>")
async def warn_check(self): ts = datetime.now(tz=timezone.utc).timestamp() if len(self.warn_db) <= 0: return for sid in self.warn_db: if len(self.warn_db[sid]) <= 0: continue guild = self.bot.get_guild(int(sid)) for uid in self.warn_db[sid]: for i, w in self.warn_db[sid][uid].items(): if ts >= float(w["expires"]): del self.warn_db[sid][uid][i] update_db(self.sql_db, self.warn_db, "warns") self.bot.log.info( f"[ADMIN][WARN][REMOVE] {uid}.{i} in <{guild.name}>" )
async def role_admin_remove(self, ctx: Context, *, role_get: Role): """Remove a role from the assignable roles list. Server administrator permission required. """ sid = str(ctx.guild.id) name = role_get.name.lower() if not await self.roles_check(ctx): return if name not in self.db[sid]["roles"]: await ctx.send( ":anger: That is not an assignable role on this server.") else: try: del self.db[sid]["roles"][name] await ctx.send( f":white_check_mark: Removed {role_get.name} from assignable roles." ) update_db(self.sql_db, self.db, "servers") except Exception as e: await ctx.send(f":anger: Error removing role: {e}")
async def mute_check(self): ts = datetime.now(tz=timezone.utc).timestamp() if len(self.mute_db) <= 0: return for sid in self.mute_db: if len(self.mute_db[sid]) <= 0: continue for uid, info in self.mute_db[sid].items(): if ts >= float(info["expires"]): guild = self.bot.get_guild(int(sid)) try: role = guild.get_role(int(self.db[sid]["mute_role"])) # Delete the mute from the database if we're unable to get the role except KeyError: del self.mute_db[sid][uid] break target = guild.get_member(int(uid)) if role in target.roles: await target.remove_roles(role, reason="Auto mute remove.") await target.send( f":speaking_head: Your mute in {guild.name} has expired." ) else: del self.mute_db[sid][uid] break del self.mute_db[sid][uid] update_db(self.sql_db, self.mute_db, "mutes") self.bot.log.info( f"[ADMIN][MUTE][REMOVE] {target.id} in <{guild.name}>" )
async def cmd_plugins_load(self, ctx: Context, name: str): """Load plugin (Cog). Do not include file extension. Botmaster required. """ if name in self.bot.plugins: await ctx.send(f":anger: Plugin {name}.py already loaded.") return if not os.path.isfile(f"plugins/{name}.py"): await ctx.send(f":anger: Cannot find plugins/{name}.py") else: try: self.copy_plugin_if_needed(name) self.bot.load_extension(f"plugins.{name}") self.bot.plugins.append(name) update_db(self.bot.db, self.bot.plugins, "plugins") await ctx.send( f":white_check_mark: Plugin {name}.py successfully loaded." ) except Exception as e: exc = f"{type(e).__name__}, {e}" await ctx.send(f":anger: Error loading {name}.py:\n```py\n{exc}\n```")
async def block(self, ctx: Context, target: User, block: bool = True): """Add or remove a user from the block list. Botmaster required. """ uid = str(target.id) if uid in self.bot.blocklist: # Trying to block a user who is already blocked if block: await ctx.send(f":anger: {target.name} is already blocked.") # Unblock a user. else: self.bot.blocklist.remove(uid) await ctx.send(f":white_check_mark: {target.name} unblocked.") else: # Add a user to the blocklist if block: self.bot.blocklist.append(uid) await ctx.send(f":white_check_mark: {target.name} blocked.") # Trying to remove a user who is not blocklisted else: await ctx.send(f":anger: {target.name} is not blocked.") update_db(self.bot.db, self.bot.blocklist, "blocklist")