def __init__(self, *, loop=None, **options): super().__init__(loop=loop, **options) self.banned_ids = [] self.config = yaml.safe_load(open("config.yml", "r")) self.data_manager = DataManager() self.interpreter = Interpreter(locals(), self)
def __init__(self, *, loop=None, **options): super().__init__(loop=loop, **options) self.banned_ids = [] self.webhooks = {} # {channel_id: webhook_url} with open("config.yml", "r") as fh: self.config = yaml.safe_load(fh) self.data_manager = DataManager() self.interpreter = Interpreter(locals(), self)
def __init__(self, bot: Bot): self.bot = bot self.env = {} self.ln = 0 self.stdout = StringIO() self.interpreter = Interpreter(bot)
class Client(discord.client.Client): def __init__(self, *, loop=None, **options): super().__init__(loop=loop, **options) self.banned_ids = [] self.config = yaml.safe_load(open("config.yml", "r")) self.data_manager = DataManager() self.interpreter = Interpreter(locals(), self) def get_token(self): return self.config["token"] def log_to_channel(self, record: logging.LogRecord): if not self.config.get("log_channel"): return channel = self.get_channel(self.config["log_channel"]) if not channel: return dt = datetime.datetime.fromtimestamp(record.created) description = record.msg if record.exc_info: description += "\n\n```{}```".format("\n".join( traceback.format_exception(*record.exc_info))) embed = Embed(title="{} / {}".format(record.name, record.levelname), description=description) if record.levelno in LOG_COLOURS: embed.colour = LOG_COLOURS[record.levelno] embed.set_footer(text=dt.strftime("%B %d %Y, %H:%M:%S")) async def inner(): await self.send_message(channel, embed=embed) self.loop.call_soon_threadsafe(asyncio. async, inner()) async def close(self): log.info("Shutting down...") self.data_manager.save() await discord.client.Client.close(self) def sections_updated(self, server): self.data_manager.save_server(server.id) async def on_ready(self): log.info("Setting up...") self.data_manager.load() for server in self.servers: self.data_manager.add_server(server.id) log.info("Ready!") async def on_server_join(self, server): log.info("Server joined: {} ({})\nOwner: {}".format( server.name, server.id, server.owner.name)) self.data_manager.add_server(server.id) for message in WELCOME_MESSAGE: await self.send_message(server.default_channel, content=message) async def on_message(self, message): if message.server is None: return # DM if message.author.id == self.user.id: return if str(message.author.discriminator ) == "0000": # Ignore webhooks and system messages return logger = logging.getLogger(message.server.name) user = "******".format(message.author.name, message.author.discriminator) for line in message.content.split("\n"): logger.debug("#{} / {} {}".format(message.channel.name, user, line)) chars = self.data_manager.get_server_command_chars(message.server) text = None if message.content.startswith(chars): # It's a command text = message.content[len(chars):].strip() elif message.content.startswith(self.user.mention): text = message.content[len(self.user.mention):].strip() if text: if " " in text: command, args = text.split(" ", 1) else: command = text args = "" args_string = args args = shlex.split(args) if len(args) > 0: data = args[0:] if GIST_REGEX.match(data[-1]): gist_id = data.pop(-1).split(":")[1] gist_url = GIST_URL.format(gist_id) log.debug("Grabbing gist info: {}".format(gist_id)) session = ClientSession() async with session.get(gist_url) as response: gist_json = await response.json() session.close() if "files" not in gist_json: return await self.send_message( message.channel, "{} No such gist: `{}`".format( message.author.mention, gist_id)) for filename, file in gist_json["files"].items(): log.debug("Gist file collected: {}".format(filename)) data.append(file["content"]) else: data = [] log.debug("Command: {}".format(repr(command))) log.debug("Args: {}".format(repr(args))) log.debug("Args string: {}".format(repr(args_string))) log.debug("Data: {}".format(repr(data))) if hasattr(self, "command_{}".format(command.replace("-", "_"))): await getattr(self, "command_{}".format( command.replace("-", "_")))(data, args_string, message) async def clear_channel(self, channel): current_index = None last_index = None num_errors = 0 while current_index != -1: if num_errors >= 5: break try: async for message in self.logs_from(channel, before=current_index): current_index = message await self.delete_message(message) except ServerDisconnectedError: try: async for message in self.logs_from(channel, before=current_index): current_index = message await self.delete_message(message) except Exception: num_errors += 1 continue except Exception: num_errors += 1 continue if last_index == current_index: break last_index = current_index def has_permission(self, user): if user.server_permissions.manage_server: return True elif int(user.id) == int(self.config["owner_id"]): return True return False def has_permission_notes(self, user): if user.server_permissions.manage_messages: return True elif int(user.id) == int(self.config["owner_id"]): return True return False def create_note_embed(self, server, note, index): embed = Embed(title="{}: {}".format(note["status"].title(), index), description=note["text"], timestamp=note["submitted"]) if note["status"] == "open": colour = Colour.blue() elif note["status"] == "closed": colour = Colour.gold() else: colour = Colour.green() embed.colour = colour user = server.get_member(note["submitter"]["id"]) if user: embed.set_footer(text=user.name, icon_url=(user.avatar_url or user.default_avatar_url)) else: embed.set_footer(text=note["submitter"]["name"]) return embed # region Commands async def command_eval(self, data, data_string, message): if int(message.author.id) != int(self.config["owner_id"]): return code = data_string.strip(" ") if code.startswith("```") and code.endswith("```"): if code.startswith("```python"): code = code[9:-3] elif code.startswith("```py"): code = code[5:-3] else: code = code[3:-3] elif code.startswith("`") and code.endswith("`"): code = code[1:-1] code = code.strip().strip("\n") lines = [] def output(line): lines.append(line) self.interpreter.set_output(output) try: rvalue = await self.interpreter.runsource(code, message) except Exception as e: await self.send_message( message.channel, "**Error**\n ```{}```\n\n**Code** \n```py\n{}\n```".format( e, code)) else: out_message = "**Returned** \n```py\n{}\n```\n\n".format( repr(rvalue)) if lines: out_message += "**Output** \n```\n{}\n```\n\n".format( "\n".join(lines)) out_message += "**Code** \n```py\n{}\n```".format(code) await self.send_message(message.channel, out_message) async def command_config(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: config = self.data_manager.get_config(message.server) md = "__**Current configuration**__\n\n" for key, value in config.items(): md += "**{}**: `{}`\n".format(key, value) await self.send_message( message.channel, "{}\n\n{}".format(message.author.mention, md)) elif len(data) < 2: config = self.data_manager.get_config(message.server) key = data[0].lower() if key not in config: return await self.send_message( message.channel, "{} Unknown key: `{}`".format(message.author.mention, key)) await self.send_message( message.channel, "{} **{}** is set to `{}`\n\n**Info**: {}".format( message.author.mention, key, config[key], CONFIG_KEY_DESCRIPTIONS[key])) else: config = self.data_manager.get_config(message.server) key, value = data[0].lower(), data[1] if key == "info_channel": return await self.send_message( message.channel, "{} Please use the `setup` command to change the info channel instead." .format(message.author.mention)) elif key == "notes_channel": return await self.send_message( message.channel, "{} Please use the `setup-notes` command to change the notes channel instead." .format(message.author.mention)) if key not in config: return await self.send_message( message.channel, "{} Unknown key: `{}`".format(message.author.mention, key)) self.data_manager.set_config(message.server, key, value) self.data_manager.save_server(message.server.id) await self.send_message( message.channel, "{} **{}** is now set to `{}`".format(message.author.mention, key, value)) async def command_create(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 2: return await self.send_message( message.channel, "{} Usage: `create <section type> \"<section name>\"`".format( message.author.mention)) section_type, section_name = data[0], data[1] if self.data_manager.get_section(message.server, section_name): return await self.send_message( message.channel, "{} A section named `{}` already exists".format( message.author.mention, section_name)) clazz = self.data_manager.get_section_class(section_type) if not clazz: return await self.send_message( message.channel, "{} Unknown section type: `{}`".format(message.author.mention, section_type)) section = clazz(section_name) self.data_manager.add_section(message.server, section) self.data_manager.save_server(message.server.id) await self.send_message( message.channel, "{} Section created: `{}`\n\nRun the `update` command to wipe the info channel and add it." .format(message.author.mention, section_name)) async def command_help(self, data, data_string, message): await self.send_message( message.channel, "{}\n\n{}".format(message.author.mention, HELP_MESSAGE)) async def command_remove(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "{} Usage: `remove \"<section name>\"`".format( message.author.mention)) section_name = data[0] if not self.data_manager.get_section(message.server, section_name): return await self.send_message( message.channel, "{} No such section: `{}`\n\nPerhaps you meant to surround the section name with " "\"quotes\"?".format(message.author.mention, section_name)) self.data_manager.remove_section(message.server, section_name) self.data_manager.save_server(message.server.id) await self.send_message( message.channel, "{} Section removed: `{}`\n\nRun the `update` command to wipe the info channel and recreate without " "it.".format(message.author.mention, section_name)) async def command_section(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms try: section_name, command = data[0], data[1] if len(data) > 2: data = data[2:] else: data = [] except Exception: return await self.send_message( message.channel, content= "{} Command usage: `section \"<section name>\" <command> [data ...]`" .format(message.author.mention)) section = self.data_manager.get_section(message.server, section_name) if not section: return await self.send_message( message.channel, content="{} No such section: {}".format( message.author.mention, section_name)) try: result = await section.process_command(command, data, data_string, self, message) except Exception: await self.send_message( message.channel, content= "{} There was an error running that command. It's been logged, but feel free " "to raise an issue.".format(message.author.mention, section_name)) log.exception( "Error running section command `{}` for section `{}` on server `{}`" .format(command, section_name, message.server.id)) else: return await self.send_message(message.channel, content="{} {}".format( message.author.mention, result)) async def command_setup(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) > 1: return await self.send_message( message.channel, content="{} Usage: `setup <channel ID>`".format( message.author.mention)) try: channel = self.get_channel(str(int(data[0]))) except Exception: return await self.send_message( message.channel, content="{} Command usage: `setup <channel ID>`".format( message.author.mention)) if channel not in message.server.channels: return await self.send_message( message.channel, content="{} Unable to find channel for ID `{}`".format( message.author.mention, data[0])) self.data_manager.set_channel(message.server, channel) await self.send_message( message.channel, content= "{} Info channel set to {}\n\nRun the `update` command to wipe and fill it. Note that you cannot " "undo this operation - **all messages in the info channel will be removed**!\n\n**__*MAKE SURE " "YOU SELECTED THE CORRECT CHANNEL!*__**".format( message.author.mention, channel.mention)) async def command_show(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms await self.send_message( message.channel, "{} Just a moment, collecting and uploading data...".format( message.author.mention)) content = """ Rendered Markdown ================= {} Command Breakdown ================= {} """ markdown = [] commands = [] sections = self.data_manager.get_sections(message.server) chars = self.data_manager.get_server_command_chars(message.server) for name, section in sections: markdown_set = ["**__{}__**".format(name)] command_set = ["add {} \"{}\"".format(section._type, name)] if section.get_header(): command_set.append("header \"{}\" \"{}\"".format( name, section.get_header())) markdown_set.append(section.get_header()) for x in await section.show(): command_set.append(x) for part in await section.render(): markdown_set.append(part) if section.get_footer(): command_set.append("footer \"{}\" \"{}\"".format( name, section.get_footer())) markdown_set.append(section.get_footer()) commands.append(command_set) markdown.append(markdown_set) final_markdown = [] final_commands = [] for m_set in markdown: final_markdown.append("\n\n".join(m_set)) for c_set in commands: final_commands.append("\n\n".join([chars + c for c in c_set])) content = content.format( "\n\n---\n\n".join(final_markdown), "\n\n---\n\n".join(final_commands), ) del markdown, commands del final_markdown, final_commands await self.send_file( message.channel, io.BytesIO(content.encode("UTF-8")), filename="data.txt", content="{} Here's the data you requested.".format( message.author.mention)) async def command_update(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms channel = self.data_manager.get_channel(message.server) if not channel: return await self.send_message( message.channel, "{} No info channel has been set for this server. Try the `setup` command!" .format(message.author.mention)) channel = self.get_channel(channel) if not channel: return await self.send_message( message.channel, "{} The configured info channel no longer exists. Set another with the `setup` command!" .format(message.author.mention)) await self.clear_channel(channel) sections = self.data_manager.get_sections(message.server) for name, section in sections: await self.send_message(channel, "**__{}__**".format(name)) await asyncio.sleep(0.2) if section.get_header(): await self.send_message(channel, section.get_header()) await asyncio.sleep(0.2) for part in await section.render(): await self.send_message(channel, part) await asyncio.sleep(0.2) if section.get_footer(): await self.send_message(channel, section.get_footer()) await asyncio.sleep(0.2) await self.send_message( message.channel, "{} The info channel has been updated!".format( message.author.mention)) async def command_swap(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 2: return await self.send_message( message.channel, "{} Usage: `swap \"<section name>\" \"<section name>\"`". format(message.author.mention)) left, right = data[0], data[1] if not self.data_manager.has_section(message.server, left): return await self.send_message( message.channel, "{} No such section: `{}`\n\nPerhaps you meant to surround the section name with " "\"quotes\"?".format(message.author.mention, left)) if not self.data_manager.has_section(message.server, right): return await self.send_message( message.channel, "{} No such section: `{}`\n\nPerhaps you meant to surround the section name with " "\"quotes\"?".format(message.author.mention, right)) self.data_manager.swap_sections(message.server, left, right) self.data_manager.save_server(message.server.id) await self.send_message( message.channel, "{} Sections swapped: `{}` and `{}`\n\nRun the `update` command to wipe the info channel and recreate" " with the new layout.".format(message.author.mention, left, right)) async def command_header(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 2: return await self.send_message( message.channel, "{} Usage: `header \"<section name>\" \"<data>\"`".format( message.author.mention)) section_name, header = data[0], data[1] if not self.data_manager.get_section(message.server, section_name): return await self.send_message( message.channel, "{} No such section: `{}`\n\nPerhaps you meant to surround the section name with " "\"quotes\"?".format(message.author.mention, section_name)) if len(header) > 2000: return await self.send_message( message.channel, "{} Section header must be less than 2000 characters in length" ) self.data_manager.get_section(message.server, section_name).set_header(header) await self.send_message( message.channel, "{} Section header updated: `{}`\n\nRun the `update` command to wipe the info channel and recreate with " "it.".format(message.author.mention, section_name)) async def command_footer(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 2: return await self.send_message( message.channel, "{} Usage: `footer \"<section name>\" \"<data>\"`".format( message.author.mention)) section_name, footer = data[0], data[1] if not self.data_manager.get_section(message.server, section_name): return await self.send_message( message.channel, "{} No such section: `{}`\n\nPerhaps you meant to surround the section name with " "\"quotes\"?".format(message.author.mention, section_name)) if len(footer) > 2000: return await self.send_message( message.channel, "{} Section footer must be less than 2000 characters in length" ) self.data_manager.get_section(message.server, section_name).set_footer(footer) await self.send_message( message.channel, "{} Section footer updated: `{}`\n\nRun the `update` command to wipe the info channel and recreate with " "it.".format(message.author.mention, section_name)) # Notes commands async def command_setup_notes(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) > 1: return await self.send_message( message.channel, content="{} Usage: `setup-notes <channel ID>`".format( message.author.mention)) try: channel = self.get_channel(str(int(data[0]))) except Exception: return await self.send_message( message.channel, content="{} Command usage: `setup-notes <channel ID>`".format( message.author.mention)) if channel not in message.server.channels: return await self.send_message( message.channel, content="{} Unable to find channel for ID `{}`".format( message.author.mention, data[0])) self.data_manager.set_notes_channel(message.server, channel) await self.send_message( message.channel, content= "{} Notes channel set to {}\n\nRun the `update-notes` command to wipe and fill it if you're " "moving channel (rather than setting up for the first time)". format(message.author.mention, channel.mention)) async def command_update_notes(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms channel = self.data_manager.get_notes_channel(message.server) if not channel: return await self.send_message( message.channel, "{} No notes channel has been set for this server. Try the `setup-notes` command!" .format(message.author.mention)) channel = self.get_channel(channel) if not channel: return await self.send_message( message.channel, "{} The configured notes channel no longer exists. Set another with the `setup-notes` command!" .format(message.author.mention)) await self.clear_channel(channel) notes = self.data_manager.get_notes(message.server) for index, note in notes.items(): sent_message = await self.send_message( channel, embed=self.create_note_embed(message.server, note, index)) note["message_id"] = sent_message.id await self.send_message( message.channel, "{} The notes channel has been updated!".format( message.author.mention)) async def command_note(self, data, data_string, message): if not self.has_permission_notes(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "{} Usage: `note \"<text>\"`".format(message.author.mention)) text = data[0] index, note = self.data_manager.create_note(message.server, message, text) self.data_manager.save_server(message.server.id) channel = self.data_manager.get_notes_channel(message.server) if channel: sent_message = await self.send_message( self.get_channel(channel), embed=self.create_note_embed(message.server, note, index)) note["message_id"] = sent_message.id await self.send_message( message.channel, "{} Note created: `{}`".format(message.author.mention, index)) else: await self.send_message( message.channel, "{} Note created: `{}`\n\n**Warning**: No notes channel has been set up. Try the `setup-notes` " "command!".format(message.author.mention, index)) async def command_reopen(self, data, data_string, message): if not self.has_permission_notes(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "{} Usage: `reopen \"<note number>\"`".format( message.author.mention)) index = data[0] note = self.data_manager.get_note(message.server, index) if not note: return await self.send_message( message.channel, "{} Unknown note ID: {}".format(message.author.mention, index)) if note["status"] == "open": return await self.send_message( message.channel, "{} That note is already open!".format(message.author.mention)) note["status"] = "open" self.data_manager.save_server(message.server.id) channel = self.data_manager.get_notes_channel(message.server) if channel: try: note_message = await self.get_message( self.get_channel(channel), note["message_id"]) except discord.NotFound: note_message = None if not note_message: return await self.send_message( message.channel, "{} Note updated: `{}`\n\n**Warning**: The message containing this note has been deleted. Use the " "`update-notes` command to repopulate the notes channel!". format(message.author.mention, index)) await self.edit_message(note_message, embed=self.create_note_embed( message.server, note, index)) await self.send_message( message.channel, "{} Note updated: `{}`".format(message.author.mention, index)) else: await self.send_message( message.channel, "{} Note updated: `{}`\n\n**Warning**: No notes channel has been set up. Try the `setup-notes` " "command!".format(message.author.mention, index)) async def command_resolve(self, data, data_string, message): if not self.has_permission_notes(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "{} Usage: `resolve \"<note number>\"`".format( message.author.mention)) index = data[0] note = self.data_manager.get_note(message.server, index) if not note: return await self.send_message( message.channel, "{} Unknown note ID: {}".format(message.author.mention, index)) if note["status"] == "resolved": return await self.send_message( message.channel, "{} That note is already resolved!".format( message.author.mention)) note["status"] = "resolved" self.data_manager.save_server(message.server.id) channel = self.data_manager.get_notes_channel(message.server) if channel: try: note_message = await self.get_message( self.get_channel(channel), note["message_id"]) except discord.NotFound: note_message = None if not note_message: return await self.send_message( message.channel, "{} Note updated: `{}`\n\n**Warning**: The message containing this note has been deleted. Use the " "`update-notes` command to repopulate the notes channel!". format(message.author.mention, index)) await self.edit_message(note_message, embed=self.create_note_embed( message.server, note, index)) await self.send_message( message.channel, "{} Note updated: `{}`".format(message.author.mention, index)) else: await self.send_message( message.channel, "{} Note updated: `{}`\n\n**Warning**: No notes channel has been set up. Try the `setup-notes` " "command!".format(message.author.mention, index)) async def command_close(self, data, data_string, message): if not self.has_permission_notes(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "{} Usage: `close \"<note number>\"`".format( message.author.mention)) index = data[0] note = self.data_manager.get_note(message.server, index) if not note: return await self.send_message( message.channel, "{} Unknown note ID: {}".format(message.author.mention, index)) if note["status"] == "closed": return await self.send_message( message.channel, "{} That note is already closed!".format( message.author.mention)) note["status"] = "closed" self.data_manager.save_server(message.server.id) channel = self.data_manager.get_notes_channel(message.server) if channel: try: note_message = await self.get_message( self.get_channel(channel), note["message_id"]) except discord.NotFound: note_message = None if not note_message: return await self.send_message( message.channel, "{} Note updated: `{}`\n\n**Warning**: The message containing this note has been deleted. Use the " "`update-notes` command to repopulate the notes channel!". format(message.author.mention, index)) await self.edit_message(note_message, embed=self.create_note_embed( message.server, note, index)) await self.send_message( message.channel, "{} Note updated: `{}`".format(message.author.mention, index)) else: await self.send_message( message.channel, "{} Note updated: `{}`\n\n**Warning**: No notes channel has been set up. Try the `setup-notes` " "command!".format(message.author.mention, index)) async def command_note_edit(self, data, data_string, message): if not self.has_permission_notes(message.author): return log.debug("Permission denied") # No perms if len(data) < 2: return await self.send_message( message.channel, "{} Usage: `note-edit \"<note number>\" \"<text>\"`".format( message.author.mention)) index, text = data[0], data[1] note = self.data_manager.get_note(message.server, index) if not note: return await self.send_message( message.channel, "{} Unknown note ID: {}".format(message.author.mention, index)) if note["submitter"]["id"] != message.author.id: if not self.has_permission(message.author): return await self.send_message( message.channel, "{} You may not edit another user's note!".format( message.author.mention)) note["text"] = text self.data_manager.save_server(message.server.id) channel = self.data_manager.get_notes_channel(message.server) if channel: try: note_message = await self.get_message( self.get_channel(channel), note["message_id"]) except discord.NotFound: note_message = None if not note_message: return await self.send_message( message.channel, "{} Note updated: `{}`\n\n**Warning**: The message containing this note has been deleted. Use " "the `update-notes` command to repopulate the notes channel!" .format(message.author.mention, index)) await self.edit_message(note_message, embed=self.create_note_embed( message.server, note, index)) await self.send_message( message.channel, "{} Note updated: `{}`".format(message.author.mention, index)) else: await self.send_message( message.channel, "{} Note updated: `{}`\n\n**Warning**: No notes channel has been set up. Try the `setup-notes` " "command!".format(message.author.mention, index)) async def command_note_delete(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "{} Usage: `note-delete \"<note number>\"`".format( message.author.mention)) index = data[0] note = self.data_manager.get_note(message.server, index) if not note: return await self.send_message( message.channel, "{} Unknown note ID: {}".format(message.author.mention, index)) self.data_manager.delete_note(message.server, index) self.data_manager.save_server(message.server) channel = self.data_manager.get_notes_channel(message.server) if channel: try: note_message = await self.get_message( self.get_channel(channel), note["message_id"]) except discord.NotFound: note_message = None if note_message: await self.delete_message(note_message) await self.send_message( message.channel, "{} Note deleted: `{}`".format(message.author.mention, index)) # Aliases command_add = command_create command_delete = command_remove # endregion pass
class Client(discord.client.Client): normal_mention = None nick_mention = None def __init__(self, *, loop=None, **options): super().__init__(loop=loop, **options) self.banned_ids = [] self.webhooks = {} # {channel_id: webhook_url} with open("config.yml", "r") as fh: self.config = yaml.safe_load(fh) self.data_manager = DataManager() self.interpreter = Interpreter(locals(), self) def get_token(self): return self.config["token"] def get_channel_info(self, channel): return "`#{}` on `{}`".format(channel.name, channel.server.name) def log_to_channel(self, record: logging.LogRecord): if not self.config.get("log_channel"): return channel = self.get_channel(self.config["log_channel"]) if not channel: return dt = datetime.datetime.fromtimestamp(record.created) description = record.msg if record.exc_info: description += "\n\n```{}```".format("\n".join( traceback.format_exception(*record.exc_info))) embed = Embed(title="{} / {}".format(record.name, record.levelname), description=description) if record.levelno in LOG_COLOURS: embed.colour = LOG_COLOURS[record.levelno] embed.set_footer(text=dt.strftime("%B %d %Y, %H:%M:%S")) async def inner(): await self.send_message(channel, embed=embed) self.loop.call_soon_threadsafe(asyncio. async, inner()) async def close(self): log.info("Shutting down...") self.data_manager.save() await discord.client.Client.close(self) def channels_updated(self, server): self.data_manager.save_server(server.id) async def on_ready(self): log.info("Setting up...") self.data_manager.load() self.normal_mention = "<@{}>".format(self.user.id) self.nick_mention = "<@!{}>".format(self.user.id) for server in self.servers: self.data_manager.add_server(server.id) log.debug("Getting webhooks...") for channel_id, targets in list(self.data_manager.channels.items()): hooks = 0 if channel_id not in self.webhooks: try: h = await self.ensure_relay_hook(channel_id) except Exception: log.exception( "Unable to get webhook for channel: `{}`".format( channel_id)) self.data_manager.remove_targets(channel_id) self.data_manager.save() continue if h is None: # Doesn't exist log.debug( "Channel {} no longer exists.".format(channel_id)) self.data_manager.remove_targets(channel_id) self.data_manager.save() continue elif h is False: # No permission await self.send_message( self.get_channel(channel_id), "**Error**: I do not have permission to manage webhooks on this channel.\n\n" "As I require this permission to function, I have entirely unlinked this channel. Please link " "it again when this is fixed.") self.data_manager.remove_targets(channel_id) self.data_manager.save() continue else: self.webhooks[channel_id] = h hooks += 1 log.debug("Got {} webhooks for channel `{}`".format( hooks, channel_id)) log.info("Ready!") async def on_server_join(self, server): self.data_manager.add_server(server.id) for message in WELCOME_MESSAGE: await self.send_message(server.default_channel, content=message) async def on_message(self, message): if message.server is None: return # DM if message.author.id == self.user.id: return if str(message.author.discriminator) == "0000": return logger = logging.getLogger(message.server.name) user = "******".format(message.author.name, message.author.discriminator) for line in message.content.split("\n"): logger.debug("#{} / {} {}".format(message.channel.name, user, line)) chars = self.data_manager.get_server_command_chars(message.server) text = None if message.content.startswith(chars): # It's a command text = message.content[len(chars):].strip() elif message.content.startswith(self.normal_mention): text = message.content[len(self.normal_mention):].strip() elif message.content.startswith(self.nick_mention): text = message.content[len(self.nick_mention):].strip() if text: if " " in text: command, args = text.split(" ", 1) else: command = text args = "" args_string = args args = shlex.split(args) if len(args) > 0: data = args[0:] else: data = [] log.debug("Command: {}".format(repr(command))) log.debug("Args: {}".format(repr(args))) log.debug("Args string: {}".format(repr(args_string))) log.debug("Data: {}".format(repr(data))) if hasattr(self, "command_{}".format(command.replace("-", "_"))): try: await getattr( self, "command_{}".format(command.replace("-", "_")))(data, args_string, message) except Exception: log.exception("Error running command: {}".format(command)) else: # We should relay this await self.do_relay(message) def has_permission(self, user): if user.server_permissions.manage_server: return True if int(user.id) == int(self.config["owner_id"]): return True return False async def do_relay(self, message): targets = self.data_manager.get_all_targets(message.channel) prefixed = self.data_manager.get_prefixes(message.channel) content = message.content lower_content = content.lower() for prefix, target in prefixed.items(): if lower_content.startswith(prefix): targets.add(target) content = content[len(prefix):] break del lower_content avatar = message.author.avatar_url for channel_id in targets: if channel_id == message.channel.id: continue hook = self.webhooks.get(channel_id, None) if hook is None: h = await self.ensure_relay_hook(channel_id) if h: self.webhooks[channel_id] = h hook = self.webhooks.get(channel_id, None) if hook is None: await self.send_message( message.channel, "Webhook for channel `{}` is missing - unlinking channel entirely" .format(channel_id)) self.data_manager.unlink_all(channel_id) self.data_manager.save() else: try: if content: await self.execute_webhook( hook["id"], hook["token"], wait=True, content=content, username=message.author.display_name, avatar_url=avatar if avatar else None, embeds=message.embeds) elif message.embeds: await self.execute_webhook( hook["id"], hook["token"], wait=True, username=message.author.display_name, avatar_url=avatar if avatar else None, embeds=message.embeds) if message.attachments: lines = ["__**Attachments**__\n"] for attachment in message.attachments: lines.append("**{}**: {}".format( attachment["filename"], attachment["url"])) for split_line in line_splitter(lines, 2000): await self.execute_webhook( hook["id"], hook["token"], wait=True, content=split_line, username=message.author.display_name, avatar_url=avatar if avatar else None) except Exception as e: await self.send_message( message.channel, "Error executing webhook for channel `{}` - unlinking channel\n\n```{}```" .format(channel_id, e)) self.data_manager.remove_targets(channel_id) self.data_manager.save() raise # region Commands async def command_config(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: config = self.data_manager.get_config(message.server) md = "__**Current configuration**__\n\n" for key, value in config.items(): md += "**{}**: `{}`\n".format(key, value) await self.send_message( message.channel, "{}\n\n{}".format(message.author.mention, md)) elif len(data) < 2: config = self.data_manager.get_config(message.server) key = data[0].lower() if key not in config: return await self.send_message( message.channel, "{} Unknown key: `{}`".format(message.author.mention, key)) await self.send_message( message.channel, "{} **{}** is set to `{}`\n\n**Info**: {}".format( message.author.mention, key, config[key], CONFIG_KEY_DESCRIPTIONS[key])) else: config = self.data_manager.get_config(message.server) key, value = data[0].lower(), data[1] if key not in config: return await self.send_message( message.channel, "{} Unknown key: `{}`".format(message.author.mention, key)) self.data_manager.set_config(message.server, key, value) self.data_manager.save_server(message.server.id) await self.send_message( message.channel, "{} **{}** is now set to `{}`".format(message.author.mention, key, value)) async def command_eval(self, data, data_string, message): if int(message.author.id) != int(self.config["owner_id"]): return code = data_string.strip(" ") if code.startswith("```") and code.endswith("```"): if code.startswith("```python"): code = code[9:-3] elif code.startswith("```py"): code = code[5:-3] else: code = code[3:-3] elif code.startswith("`") and code.endswith("`"): code = code[1:-1] code = code.strip().strip("\n") lines = [] def output(line): lines.append(line) self.interpreter.set_output(output) try: rvalue = await self.interpreter.runsource(code, message) except Exception as e: await self.send_message( message.channel, "**Error**\n ```{}```\n\n**Code** \n```py\n{}\n```".format( e, code)) else: out_message = "**Returned** \n```py\n{}\n```\n\n".format( repr(rvalue)) if lines: out_message += "**Output** \n```\n{}\n```\n\n".format( "\n".join(lines)) out_message += "**Code** \n```py\n{}\n```".format(code) await self.send_message(message.channel, out_message) async def command_help(self, data, data_string, message): await self.send_message( message.channel, "{} {}".format(message.author.mention, HELP_MESSAGE)) async def command_link(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "Usage: `link <channel ID> [channel ID]`") await self.send_typing(message.channel) if len(data) < 2: left = message.channel right = data[0] else: left, right = data[0], data[1] try: int(left) left = self.get_channel(left) except Exception: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(left)) try: int(right) right = self.get_channel(right) except Exception: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(right)) left_member = left.server.get_member(message.author.id) right_member = right.server.get_member(message.author.id) if left_member is None: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(left.id)) elif right_member is None: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(right.id)) if left.id == right.id: return await self.send_message( message.channel, "You may not link a channel to itself!") if self.data_manager.has_target(left, right): return await self.send_message( message.channel, "These channels are already linked!") if left_member.server_permissions.manage_server and right_member.server_permissions.manage_server: try: h = await self.ensure_relay_hook(left) if not h: await self.send_message( message.channel, "Unable to set up webhook for {}: I don't have the Manage Webhooks " "permission.".format(self.get_channel_info(left))) self.webhooks[left.id] = h except Exception as e: await self.send_message( message.channel, "Unable to set up webhook for channel {}: `{}`".format( self.get_channel_info(left), e)) raise try: h = await self.ensure_relay_hook(right) if not h: return await self.send_message( message.channel, "Unable to set up webhook for {}: I don't have the Manage Webhooks " "permission.".format(self.get_channel_info(right))) self.webhooks[right.id] = h except Exception as e: return await self.send_message( message.channel, "Unable to set up webhook for {}: `{}`".format( self.get_channel_info(right), e)) self.data_manager.add_target(left, right) self.data_manager.save() await self.send_message(message.channel, "Channels linked successfully.") if left.id != message.channel.id: await self.send_message( left, "This channel has been linked to {} by {}.".format( self.get_channel_info(right), message.author.mention)) if right.id != message.channel.id: await self.send_message( right, "This channel has been linked to {} by {}.".format( self.get_channel_info(left), message.author.mention)) else: return await self.send_message( message.channel, "Permission denied - you must have `Manage Server` on the server belonging to both channels" ) async def command_relay(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "Usage: `relay <origin[|target]> [target]`") await self.send_typing(message.channel) if len(data) < 2: left = message.channel right = data[0] else: left, right = data[0], data[1] try: int(left) left = self.get_channel(left) except Exception: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(left)) try: int(right) right = self.get_channel(right) except Exception: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(right)) left_member = left.server.get_member(message.author.id) right_member = right.server.get_member(message.author.id) if left_member is None: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(left.id)) elif right_member is None: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(right.id)) if left.id == right.id: return await self.send_message( message.channel, "You may not relay a channel to itself!") if self.data_manager.has_relay(left, right): return await self.send_message( message.channel, "These channels are already relayed in that direction!") if left_member.server_permissions.manage_server and right_member.server_permissions.manage_server: try: h = await self.ensure_relay_hook(right) if not h: return await self.send_message( message.channel, "Unable to set up webhook for {}: I don't have the Manage Webhooks " "permission.".format(self.get_channel_info(right))) self.webhooks[right.id] = h except Exception as e: return await self.send_message( message.channel, "Unable to set up webhook for {}: `{}`".format( self.get_channel_info(right), e)) self.data_manager.add_relay(left, right) self.data_manager.save() await self.send_message(message.channel, "Channels set to relay successfully.") if left.id != message.channel.id: await self.send_message( left, "This channel has been set to relay to {} by {}.".format( self.get_channel_info(right), message.author.mention)) if right.id != message.channel.id: await self.send_message( right, "This channel has been set to relay to {} by {}.".format( self.get_channel_info(left), message.author.mention)) else: return await self.send_message( message.channel, "Permission denied - you must have `Manage Server` on the server belonging to both channels" ) async def command_unrelay(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "Usage: `unrelay <origin[|target]> [target]`") await self.send_typing(message.channel) if len(data) < 2: left = message.channel right = data[0] else: left, right = data[0], data[1] try: int(left) left = self.get_channel(left) except Exception: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(left)) try: int(right) right = self.get_channel(right) except Exception: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(right)) left_member = left.server.get_member(message.author.id) right_member = right.server.get_member(message.author.id) if left_member is None: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(left.id)) elif right_member is None: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(right.id)) if left_member.server_permissions.manage_server or right_member.server_permissions.manage_server: if self.data_manager.has_relay(left, right): self.data_manager.remove_relay(left, right) self.data_manager.save() await self.send_message(message.channel, "Channel relay removed successfully.") if left.id != message.channel.id: await self.send_message( left, "This channel is no longer relayed to {} - action by {}." .format(self.get_channel_info(right), message.author.mention)) else: return await self.send_message( message.channel, "These channels are not relayed.") else: return await self.send_message( message.channel, "Permission denied - you must have `Manage Server` on the server belonging to at least one of those " "channels.") async def command_group(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "Usage: `group <group> [channel ID]`") await self.send_typing(message.channel) if len(data) < 2: channel = message.channel group = data[0] else: group, channel = data[0], data[1] try: int(channel) channel = self.get_channel(channel) except Exception: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(channel)) left_member = channel.server.get_member(message.author.id) if left_member is None: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(channel.id)) if not left_member.server_permissions.manage_server: return await self.send_message( message.channel, "Permission denied - you must have `Manage Server` on the server belonging to every channel in " "the group.") for grouped_channel in self.data_manager.get_channels_for_group(group): c = self.get_channel(grouped_channel) if not c: continue c_member = c.server.get_member(message.author.id) if c_member is None or not c_member.server_permissions.manage_server: return await self.send_message( message.channel, "Permission denied - you must have `Manage Server` on the server belonging to every channel in " "the group.") if self.data_manager.is_grouped_channel(group, channel): return await self.send_message( message.channel, "This channel is already in the `{}` group.".format(group)) try: h = await self.ensure_relay_hook(channel) if not h: return await self.send_message( message.channel, "Unable to set up webhook for {}: I don't have the Manage Webhooks " "permission.".format(self.get_channel_info(channel))) self.webhooks[channel.id] = h except Exception as e: return await self.send_message( message.channel, "Unable to set up webhook for {}: `{}`".format( self.get_channel_info(channel), e)) self.data_manager.group_channel(group, channel) self.data_manager.save() await self.send_message( message.channel, "Channel added to the `{}` group successfully.".format(group)) if channel.id != message.channel.id: await self.send_message( channel, "This channel has been added to the relay group `{}` by {}.". format(group, message.author.mention)) for grouped_channel in self.data_manager.get_channels_for_group(group): if grouped_channel == message.channel.id: continue c = self.get_channel(grouped_channel) if not c: continue await self.send_message( c, "This channel is now being relayed to {} via the relay group `{}` at the request of {}" .format(self.get_channel_info(channel), group, message.author.mention)) async def command_ungroup(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "Usage: `ungroup <group> [channel ID]`") await self.send_typing(message.channel) if len(data) < 2: channel = message.channel group = data[0] else: group, channel = data[0], data[1] try: int(channel) channel = self.get_channel(channel) except Exception: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(channel)) left_member = channel.server.get_member(message.author.id) if left_member is None: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(channel.id)) if not left_member.server_permissions.manage_server: return await self.send_message( message.channel, "Permission denied - you must have `Manage Server` on the channel you wish to ungroup." ) if not self.data_manager.is_grouped_channel(group, channel): return await self.send_message( message.channel, "This channel is not in the `{}` group.".format(group)) self.data_manager.ungroup_channel(group, channel) self.data_manager.save() await self.send_message( message.channel, "Channel removed from the `{}` group successfully.".format(group)) if channel.id != message.channel.id: await self.send_message( channel, "This channel has been removed from the relay group `{}` by {}." .format(group, message.author.mention)) for grouped_channel in self.data_manager.get_channels_for_group(group): if grouped_channel == message.channel.id: continue c = self.get_channel(grouped_channel) if not c: continue await self.send_message( c, "This channel is no longer being relayed to {} via the relay group `{}` at the request of {}." .format(self.get_channel_info(channel), group, message.author.mention)) async def command_prefix(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 2: return await self.send_message( message.channel, "Usage: `prefix <origin[|target]> <prefix> [target]`") await self.send_typing(message.channel) if len(data) < 3: left = message.channel right, prefix = data[0], data[1] else: left, prefix, right = data[0], data[1], data[2] try: int(left) left = self.get_channel(left) except Exception: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(left)) try: int(right) right = self.get_channel(right) except Exception: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(right)) left_member = left.server.get_member(message.author.id) right_member = right.server.get_member(message.author.id) if left_member is None: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(left.id)) elif right_member is None: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(right.id)) if left.id == right.id: return await self.send_message( message.channel, "You may not relay a channel to itself!") if self.data_manager.has_specific_prefix(left, right, prefix): return await self.send_message( message.channel, "These channels are already relayed using that prefix!") if left_member.server_permissions.manage_server and right_member.server_permissions.manage_server: try: h = await self.ensure_relay_hook(right) if not h: return await self.send_message( message.channel, "Unable to set up webhook for {}: I don't have the Manage Webhooks " "permission.".format(self.get_channel_info(right))) self.webhooks[right.id] = h except Exception as e: return await self.send_message( message.channel, "Unable to set up webhook for {}: `{}`".format( self.get_channel_info(right), e)) self.data_manager.set_prefix(left, right, prefix) self.data_manager.save() await self.send_message( message.channel, "Channels set to relay using a prefix successfully.") if left.id != message.channel.id: await self.send_message( left, "This channel has been set to relay to {} by {} using the prefix `{}`." .format(self.get_channel_info(right), message.author.mention, prefix)) if right.id != message.channel.id: await self.send_message( right, "This channel has been set to be relayed to from {} by {} using the prefix `{}`." .format(self.get_channel_info(left), message.author.mention, prefix)) else: return await self.send_message( message.channel, "Permission denied - you must have `Manage Server` on the server belonging to both channels" ) async def command_unprefix(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "Usage: `unrelay <origin[|prefix]> [prefix]`") await self.send_typing(message.channel) if len(data) < 2: left = message.channel prefix = data[0] else: left, prefix = data[0], data[1] try: int(left) left = self.get_channel(left) except Exception: return await self.send_message( message.channel, "Invalid channel ID: `{}`".format(left)) if not self.data_manager.has_prefix(left, prefix): return await self.send_message( message.channel, "No channel is linked using prefix `{}`".format(prefix)) right = self.get_channel( self.data_manager.get_prefixed_target(left, prefix)) left_member = left.server.get_member(message.author.id) right_member = right.server.get_member(message.author.id) if left_member is None and right_member is None: return await self.send_message( message.channel, "You must be on the server belonging to at least one of the channels." ) if left_member.server_permissions.manage_server or right_member.server_permissions.manage_server: if self.data_manager.has_specific_prefix(left, right, prefix): self.data_manager.remove_prefix(left, prefix) self.data_manager.save() await self.send_message( message.channel, "Prefixed relay removed successfully.") if left.id != message.channel.id: await self.send_message( left, "This channel is no longer relayed to {} by prefix - action by {}." .format(self.get_channel_info(right), message.author.mention)) else: return await self.send_message( message.channel, "These channels are not relayed by prefix.") else: return await self.send_message( message.channel, "Permission denied - you must have `Manage Server` on the server belonging to at least one of those " "channels.") async def command_links(self, data, data_string, message): links = self.data_manager.get_targets(message.channel) relays = self.data_manager.get_relays(message.channel) groups = self.data_manager.find_groups(message.channel) prefixes = self.data_manager.get_prefixes(message.channel) lines = [] if not links and not relays and not groups: return await self.send_message( message.channel, "This channel is not linked to any others in any way.") if links: lines.append("**Two-way linked channels**") for target in links: channel = self.get_channel(target) if not channel: lines.append("• {}".format(target)) else: lines.append("• {}".format(self.get_channel_info(channel))) else: lines.append("**No two-way linked channels**") lines.append("") if relays: lines.append("**One-way relay channels**") for target in relays: channel = self.get_channel(target) if not channel: lines.append("• {}".format(target)) else: lines.append("• {}".format(self.get_channel_info(channel))) else: lines.append("**No one-way relay channels**") lines.append("") if groups: lines.append("**Channel groups**") for group in groups: channels = self.data_manager.get_channels_for_group(group) if channels: lines.append("_Group: `{}`_".format(group)) for target in channels: if target == message.channel.id: continue channel = self.get_channel(target) if not channel: lines.append("• {}".format(target)) else: lines.append("• {}".format( self.get_channel_info(channel))) else: lines.append("_Group: `{}`_ - No other channels in group") else: lines.append("**No grouped channels**") if prefixes: lines.append("**Prefix links**") for prefix, target in prefixes.items(): channel = self.get_channel(target) if not channel: lines.append("• `{}` -> {}".format(prefix, target)) else: lines.append("• `{}` -> {}".format( prefix, self.get_channel_info(channel))) else: lines.append("**No channels linked by prefix**") for line in line_splitter(lines, 2000): await self.send_message(message.channel, line) async def command_unlink(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message( message.channel, "Usage: `unlink <channel ID> [channel ID]`") await self.send_typing(message.channel) if len(data) < 2: left = message.channel right = data[0] else: left, right = data[0], data[1] try: int(left) left = self.get_channel(left) except Exception: return await self.send_message( "Invalid channel ID: `{}`".format(left)) try: int(right) right = self.get_channel(right) except Exception: return await self.send_message( "Invalid channel ID: `{}`".format(right)) left_member = left.server.get_member(message.author.id) right_member = right.server.get_member(message.author.id) if left_member is None: return await self.send_message("Invalid channel ID: `{}`".format( left.id)) elif right_member is None: return await self.send_message("Invalid channel ID: `{}`".format( right.id)) if left_member.server_permissions.manage_server or right_member.server_permissions.manage_server: if self.data_manager.has_target(left, right): self.data_manager.remove_target(left, right) self.data_manager.save() await self.send_message(message.channel, "Channels unlinked successfully.") if left.id != message.channel.id: await self.send_message( left, "This channel has been unlinked from {} by {}.".format( self.get_channel_info(right), message.author.mention)) if right.id != message.channel.id: await self.send_message( right, "This channel has been unlinked from {} by {}.".format( self.get_channel_info(left), message.author.mention)) else: return await self.send_message( message.channel, "These channels are not linked.") else: return await self.send_message( message.channel, "Permission denied - you must have `Manage Server` on the server belonging to at least one of those " "channels.") async def command_unlink_all(self, data, data_string, message): if not self.has_permission(message.author): return log.debug("Permission denied") # No perms if len(data) < 1: return await self.send_message(message.channel, "Usage: `unlink-all <channel ID>`") await self.send_typing(message.channel) channel = data[0] try: int(data[0]) channel = self.get_channel(data[0]) except Exception: return await self.send_message( message.channel, "Invalid channel ID: {}".format(channel)) if not channel.server.get_member( message.author.id).server_permissions.manage_server: return await self.send_message( message.channel, "Permission denied - you must have `Manage Server` on the server belonging to that channel." ) targets = self.data_manager.get_all_targets(channel).copy() if message.channel.id in targets: targets.remove(message.channel.id) if not targets: return await self.send_message( message.channel, "This channel is not linked to any others.") self.data_manager.unlink_all(channel) self.data_manager.save() await self.send_message(message.channel, "Notifying linked channels of removal...") await self.send_typing(message.channel) for target in targets: other = self.get_channel(target) if not other: continue await self.send_message( other, "This channel has been unlinked from {} by {} (`{}#{}`).". format(self.get_channel_info(channel), message.author.mention, message.author.name, message.author.discriminator)) await self.send_message(message.channel, "Channels unlinked successfully.") # endregion # region: Webhook management methods async def ensure_relay_hook(self, channel): if isinstance(channel, str): channel = self.get_channel(channel) if not channel: return None ourselves = channel.server.get_member(self.user.id) if not channel.permissions_for(ourselves).manage_webhooks: if not ourselves.server_permissions.manage_webhooks: return False hooks = await self.get_channel_webhooks(channel) for h in hooks: if h["name"] == "_relay": return h return await self.create_webhook(channel, name="_relay", avatar=None) # TODO: Avatar # endregion # region: Webhook HTTP methods async def create_webhook(self, channel, name=None, avatar=None) -> Dict: if isinstance(channel, Channel): channel = channel.id r = Route("POST", "/channels/{channel_id}/webhooks".format(channel_id=channel)) payload = {} if name: payload["name"] = name if avatar: payload["avatar"] = avatar if not payload: raise KeyError("Must include either `name`, `avatar`, or both") data = await self.http.request(r, json=payload) log.debug("Create Webhook [{}, {}, {}] -> {}".format( channel, name, avatar, data)) return data async def get_channel_webhooks(self, channel) -> List[Dict]: if isinstance(channel, Channel): channel = channel.id r = Route("GET", "/channels/{channel_id}/webhooks".format(channel_id=channel)) return await self.http.request(r) async def get_guild_webhooks(self, guild) -> List[Dict]: if isinstance(guild, Server): guild = guild.id r = Route("GET", "/guilds/{guild_id}/webhooks".format(guild_id=guild)) return await self.http.request(r) async def get_webhook(self, webhook_id, webhook_token) -> Dict: if not webhook_token: r = Route("GET", "/webhooks/{webhook_id}".format(webhook_id=webhook_id)) else: r = Route( "GET", "/webhooks/{webhook_id}/{webhook_token}".format( webhook_id=webhook_id, webhook_token=webhook_token)) return await self.http.request(r) async def modify_webhook(self, webhook_id, webhook_token=None, name=None, avatar=None) -> Dict: if not webhook_token: r = Route("PATCH", "/webhooks/{webhook_id}".format(webhook_id=webhook_id)) else: r = Route( "PATCH", "/webhooks/{webhook_id}/{webhook_token}".format( webhook_id=webhook_id, webhook_token=webhook_token)) payload = {} if name: payload["name"] = name if avatar: payload["avatar"] = avatar if not payload: raise KeyError("Must include either `name`, `avatar`, or both") return await self.http.request(r, json=payload) async def delete_webhook(self, webhook_id, webhook_token=None) -> None: if not webhook_token: r = Route("DELETE", "/webhooks/{webhook_id}".format(webhook_id=webhook_id)) else: r = Route( "DELETE", "/webhooks/{webhook_id}/{webhook_token}".format( webhook_id=webhook_id, webhook_token=webhook_token)) await self.http.request(r) async def execute_webhook(self, webhook_id, webhook_token, *, wait=False, content=None, username=None, avatar_url=None, tts=False, file=None, embeds=None) -> None: r = Route( "POST", "/webhooks/{webhook_id}/{webhook_token}".format( webhook_id=webhook_id, webhook_token=webhook_token)) payload = { "content": content, "username": username, "avatar_url": avatar_url, "tts": tts, "file": file, "embeds": embeds } for key, value in payload.copy().items(): if value is None: del payload[key] found = False for key in ["content", "file", "embeds"]: if key in payload: found = True if not found: raise KeyError( "Must include at least one of `content`, `embeds` or `file`") await self.http.request(r, json=payload, params={"wait": str(wait).lower()}) # endregion pass