import discord from discord.ext import commands from .utils.dataIO import fileIO from .utils import checks from __main__ import send_cmd_help, settings import os import logging import json class Mod: """Moderation tools.""" def __init__(self, bot): self.bot = bot self.whitelist_list = fileIO("data/mod/whitelist.json", "load") self.blacklist_list = fileIO("data/mod/blacklist.json", "load") self.ignore_list = fileIO("data/mod/ignorelist.json", "load") self.filter = fileIO("data/mod/filter.json", "load") @commands.group(pass_context=True,no_pm=True) @checks.admin_or_permissions(manage_server=True) async def modset(self,ctx): """Manages server administration settings.""" if ctx.invoked_subcommand is None: await send_cmd_help(ctx) msg = "```" for k, v in settings.get_server(ctx.message.server).items(): msg += str(k) + ": " + str(v) + "\n" msg += "```" await self.bot.say(msg) @modset.command(name="adminrole",pass_context=True,no_pm=True) async def _modset_adminrole(self,ctx,role_name : str): """Sets the admin role for this server, case insensitive.""" server = ctx.message.server if server.id not in settings.servers: await self.bot.say("Remember to set modrole too.") settings.set_server_admin(server,role_name) await self.bot.say("Admin role set to '{}'".format(role_name)) @modset.command(name="modrole",pass_context=True,no_pm=True) async def _modset_modrole(self,ctx,role_name : str): """Sets the mod role for this server, case insensitive.""" server = ctx.message.server if server.id not in settings.servers: await self.bot.say("Remember to set adminrole too.") settings.set_server_mod(server,role_name) await self.bot.say("Mod role set to '{}'".format(role_name)) @commands.command(no_pm=True, pass_context=True) @checks.admin_or_permissions(kick_members=True) async def kick(self, ctx, user : discord.Member): """Kicks user.""" author = ctx.message.author try: await self.bot.kick(user) logger.info("{}({}) kicked {}({})".format(author.name, author.id, user.name, user.id)) await self.bot.say("Done. That felt good.") except discord.errors.Forbidden: await self.bot.say("I'm not allowed to do that.") except Exception as e: print(e) @commands.command(no_pm=True, pass_context=True) @checks.admin_or_permissions(ban_members=True) async def ban(self, ctx, user : discord.Member, days : int=0): """Bans user and deletes last X days worth of messages. Minimum 0 days, maximum 7. Defaults to 0.""" author = ctx.message.author if days < 0 or days > 7: await self.bot.say("Invalid days. Must be between 0 and 7.") return try: await self.bot.ban(user, days) logger.info("{}({}) banned {}({}), deleting {} days worth of messages".format(author.name, author.id, user.name, user.id, str(days))) await self.bot.say("Done. It was about time.") except discord.errors.Forbidden: await self.bot.say("I'm not allowed to do that.") except Exception as e: print(e) @commands.group(pass_context=True, no_pm=True) @checks.mod_or_permissions(manage_messages=True) async def cleanup(self, ctx): """Deletes messages. cleanup messages [number] cleanup user [name/mention] [number] cleanup text \"Text here\" [number]""" if ctx.invoked_subcommand is None: await send_cmd_help(ctx) @cleanup.command(pass_context=True, no_pm=True) async def text(self, ctx, text : str, number : int): """Deletes last X messages matching the specified text. Example: cleanup text \"test\" 5 Remember to use double quotes.""" author = ctx.message.author message = ctx.message cmdmsg = message logger.info("{}({}) deleted {} messages containing '{}' in channel {}".format(author.name, author.id, str(number), text, message.channel.name)) if number > 0 and number < 10000: while True: new = False async for x in self.bot.logs_from(message.channel, limit=100, before=message): if number == 0: await self.bot.delete_message(cmdmsg) return if text in x.content: await self.bot.delete_message(x) number -= 1 new = True message = x if not new or number == 0: await self.bot.delete_message(cmdmsg) break @cleanup.command(pass_context=True, no_pm=True) async def user(self, ctx, user : discord.Member, number : int): """Deletes last X messages from specified user. Examples: cleanup user @\u200bTwentysix 2 cleanup user Red 6""" author = ctx.message.author message = ctx.message cmdmsg = message logger.info("{}({}) deleted {} messages made by {}({}) in channel {}".format(author.name, author.id, str(number), user.name, user.id, message.channel.name)) if number > 0 and number < 10000: while True: new = False async for x in self.bot.logs_from(message.channel, limit=100, before=message): if number == 0: await self.bot.delete_message(cmdmsg) return if x.author.id == user.id: await self.bot.delete_message(x) number -= 1 new = True message = x if not new or number == 0: await self.bot.delete_message(cmdmsg) break @cleanup.command(pass_context=True, no_pm=True) async def messages(self, ctx, number : int): """Deletes last X messages. Example: cleanup messages 26""" author = ctx.message.author channel = ctx.message.channel logger.info("{}({}) deleted {} messages in channel {}".format(author.name, author.id, str(number), channel.name)) if number > 0 and number < 10000: async for x in self.bot.logs_from(channel, limit=number+1): await self.bot.delete_message(x) @commands.group(pass_context=True) @checks.admin_or_permissions(ban_members=True) async def blacklist(self, ctx): """Bans user from using the bot""" if ctx.invoked_subcommand is None: await send_cmd_help(ctx) @blacklist.command(name="add") async def _blacklist_add(self, user : discord.Member): """Adds user to bot's blacklist""" if user.id not in self.blacklist_list: self.blacklist_list.append(user.id) fileIO("data/mod/blacklist.json", "save", self.blacklist_list) await self.bot.say("User has been added to blacklist.") else: await self.bot.say("User is already blacklisted.") @blacklist.command(name="remove") async def _blacklist_remove(self, user : discord.Member): """Removes user to bot's blacklist""" if user.id in self.blacklist_list: self.blacklist_list.remove(user.id) fileIO("data/mod/blacklist.json", "save", self.blacklist_list) await self.bot.say("User has been removed from blacklist.") else: await self.bot.say("User is not in blacklist.") @commands.group(pass_context=True) @checks.admin_or_permissions(ban_members=True) async def whitelist(self, ctx): """Users who will be able to use the bot""" if ctx.invoked_subcommand is None: await send_cmd_help(ctx) @whitelist.command(name="add") async def _whitelist_add(self, user : discord.Member): """Adds user to bot's whitelist""" if user.id not in self.whitelist_list: if not self.whitelist_list: msg = "\nAll users not in whitelist will be ignored (owner, admins and mods excluded)" else: msg = "" self.whitelist_list.append(user.id) fileIO("data/mod/whitelist.json", "save", self.whitelist_list) await self.bot.say("User has been added to whitelist." + msg) else: await self.bot.say("User is already whitelisted.") @whitelist.command(name="remove") async def _whitelist_remove(self, user : discord.Member): """Removes user to bot's whitelist""" if user.id in self.whitelist_list: self.whitelist_list.remove(user.id) fileIO("data/mod/whitelist.json", "save", self.whitelist_list) await self.bot.say("User has been removed from whitelist.") else: await self.bot.say("User is not in whitelist.") @commands.group(pass_context=True, no_pm=True) @checks.admin_or_permissions(manage_channels=True) async def ignore(self, ctx): """Adds servers/channels to ignorelist""" if ctx.invoked_subcommand is None: await send_cmd_help(ctx) await self.bot.say(self.count_ignored()) @ignore.command(name="channel", pass_context=True) async def ignore_channel(self, ctx, channel : discord.Channel=None): """Ignores channel Defaults to current one""" current_ch = ctx.message.channel if not channel: if current_ch.id not in self.ignore_list["CHANNELS"]: self.ignore_list["CHANNELS"].append(current_ch.id) fileIO("data/mod/ignorelist.json", "save", self.ignore_list) await self.bot.say("Channel added to ignore list.") else: await self.bot.say("Channel already in ignore list.") else: if channel.id not in self.ignore_list["CHANNELS"]: self.ignore_list["CHANNELS"].append(channel.id) fileIO("data/mod/ignorelist.json", "save", self.ignore_list) await self.bot.say("Channel added to ignore list.") else: await self.bot.say("Channel already in ignore list.") @ignore.command(name="server", pass_context=True) async def ignore_server(self, ctx): """Ignores current server""" server = ctx.message.server if server.id not in self.ignore_list["SERVERS"]: self.ignore_list["SERVERS"].append(server.id) fileIO("data/mod/ignorelist.json", "save", self.ignore_list) await self.bot.say("This server has been added to the ignore list.") else: await self.bot.say("This server is already being ignored.") @commands.group(pass_context=True, no_pm=True) @checks.admin_or_permissions(manage_channels=True) async def unignore(self, ctx): """Removes servers/channels from ignorelist""" if ctx.invoked_subcommand is None: await send_cmd_help(ctx) await self.bot.say(self.count_ignored()) @unignore.command(name="channel", pass_context=True) async def unignore_channel(self, ctx, channel : discord.Channel=None): """Removes channel from ignore list Defaults to current one""" current_ch = ctx.message.channel if not channel: if current_ch.id in self.ignore_list["CHANNELS"]: self.ignore_list["CHANNELS"].remove(current_ch.id) fileIO("data/mod/ignorelist.json", "save", self.ignore_list) await self.bot.say("This channel has been removed from the ignore list.") else: await self.bot.say("This channel is not in the ignore list.") else: if channel.id in self.ignore_list["CHANNELS"]: self.ignore_list["CHANNELS"].remove(channel.id) fileIO("data/mod/ignorelist.json", "save", self.ignore_list) await self.bot.say("Channel removed from ignore list.") else: await self.bot.say("That channel is not in the ignore list.") @unignore.command(name="server", pass_context=True) async def unignore_server(self, ctx): """Removes current server from ignore list""" server = ctx.message.server if server.id in self.ignore_list["SERVERS"]: self.ignore_list["SERVERS"].remove(server.id) fileIO("data/mod/ignorelist.json", "save", self.ignore_list) await self.bot.say("This server has been removed from the ignore list.") else: await self.bot.say("This server is not in the ignore list.") def count_ignored(self): msg = "```Currently ignoring:\n" msg += str(len(self.ignore_list["CHANNELS"])) + " channels\n" msg += str(len(self.ignore_list["SERVERS"])) + " servers\n```\n" return msg @commands.group(name="filter", pass_context=True, no_pm=True) @checks.mod_or_permissions(manage_messages=True) async def _filter(self, ctx): """Adds/removes words from filter Use double quotes to add/remove sentences Using this command with no subcommands will send the list of the server's filtered words.""" if ctx.invoked_subcommand is None: await send_cmd_help(ctx) server = ctx.message.server author = ctx.message.author msg = "" if server.id in self.filter.keys(): if self.filter[server.id] != []: word_list = self.filter[server.id] for w in word_list: msg += '"' + w + '" ' await self.bot.send_message(author, "Words filtered in this server: " + msg) @_filter.command(name="add", pass_context=True) async def filter_add(self, ctx, *words : str): """Adds words to the filter Use double quotes to add sentences Examples: filter add word1 word2 word3 filter add \"This is a sentence\"""" if words == (): await send_cmd_help(ctx) return server = ctx.message.server added = 0 if server.id not in self.filter.keys(): self.filter[server.id] = [] for w in words: if w.lower() not in self.filter[server.id] and w != "": self.filter[server.id].append(w.lower()) added += 1 if added: fileIO("data/mod/filter.json", "save", self.filter) await self.bot.say("Words added to filter.") else: await self.bot.say("Words already in the filter.") @_filter.command(name="remove", pass_context=True) async def filter_remove(self, ctx, *words : str): """Remove words from the filter Use double quotes to remove sentences Examples: filter remove word1 word2 word3 filter remove \"This is a sentence\"""" if words == (): await send_cmd_help(ctx) return server = ctx.message.server removed = 0 if server.id not in self.filter.keys(): await self.bot.say("There are no filtered words in this server.") return for w in words: if w.lower() in self.filter[server.id]: self.filter[server.id].remove(w.lower()) removed += 1 if removed: fileIO("data/mod/filter.json", "save", self.filter) await self.bot.say("Words removed from filter.") else: await self.bot.say("Those words weren't in the filter.") @commands.group(no_pm=True, pass_context=True) @checks.admin_or_permissions(manage_roles=True) async def editrole(self, ctx): if ctx.invoked_subcommand is None: await send_cmd_help(ctx) @editrole.command(aliases=["color"], pass_context=True) async def colour(self, ctx, role : discord.Role, value : discord.Colour): """Edits a role's colour Use double quotes if the role contains spaces. Colour must be in hexadecimal format. \"http://www.w3schools.com/colors/colors_picker.asp\" #cefdf9 -> 0xcefdf9 Examples: !editrole colour \"The Transistor\" 0xffff00 !editrole colour Test 0xcefdf9""" author = ctx.message.author try: await self.bot.edit_role(ctx.message.server, role, color=value) logger.info("{}({}) changed the colour of role '{}'".format(author.name, author.id, role.name)) await self.bot.say("Done.") except discord.Forbidden: await self.bot.say("I need permissions to manage roles first.") except Exception as e: print(e) await self.bot.say("Something went wrong.") @editrole.command(name="name", pass_context=True) async def edit_role_name(self, ctx, role : discord.Role, name : str): """Edits a role's name Use double quotes if the role or the name contain spaces. Examples: !editrole name \"The Transistor\" Test""" if name == "": await self.bot.say("Name cannot be empty.") return try: author = ctx.message.author old_name = role.name # probably not necessary? await self.bot.edit_role(ctx.message.server, role, name=name) logger.info("{}({}) changed the name of role '{}' to '{}'".format(author.name, author.id, old_name, name)) await self.bot.say("Done.") except discord.Forbidden: await self.bot.say("I need permissions to manage roles first.") except Exception as e: print(e) await self.bot.say("Something went wrong.") def immune_from_filter(self, message): user = message.author server = message.server admin_role = settings.get_server_admin(server) mod_role = settings.get_server_mod(server) if user.id == settings.owner: return True elif discord.utils.get(user.roles, name=admin_role): return True elif discord.utils.get(user.roles, name=mod_role): return True else: return False async def check_filter(self, message): if message.channel.is_private: return server = message.server can_delete = message.channel.permissions_for(server.me).manage_messages if message.author.id == self.bot.user.id or self.immune_from_filter(message) or not can_delete: # Owner, admins and mods are immune to the filter return if server.id in self.filter.keys(): for w in self.filter[server.id]: if w in message.content.lower(): try: # Something else in discord.py is throwing a 404 error after deletion await self.bot.delete_message(message) except: pass print("Message deleted. Filtered: " + w ) def check_folders(): folders = ("data", "data/mod/") for folder in folders: if not os.path.exists(folder): print("Creating " + folder + " folder...") os.makedirs(folder) def check_files(): ignore_list = {"SERVERS" : [], "CHANNELS" : []} if not os.path.isfile("data/mod/blacklist.json"): print("Creating empty blacklist.json...") fileIO("data/mod/blacklist.json", "save", []) if not os.path.isfile("data/mod/whitelist.json"): print("Creating empty whitelist.json...") fileIO("data/mod/whitelist.json", "save", []) if not os.path.isfile("data/mod/ignorelist.json"): print("Creating empty ignorelist.json...") fileIO("data/mod/ignorelist.json", "save", ignore_list) if not os.path.isfile("data/mod/filter.json"): print("Creating empty filter.json...") fileIO("data/mod/filter.json", "save", {}) def setup(bot): global logger check_folders() check_files() logger = logging.getLogger("mod") if logger.level == 0: # Prevents the logger from being loaded again in case of module reload logger.setLevel(logging.INFO) handler = logging.FileHandler(filename='data/mod/mod.log', encoding='utf-8', mode='a') handler.setFormatter(logging.Formatter('%(asctime)s %(message)s', datefmt="[%d/%m/%Y %H:%M]")) logger.addHandler(handler) n = Mod(bot) bot.add_listener(n.check_filter, "on_message") bot.add_cog(n)