From 7ff0153c9d1b93cdeff9d120317ce8bda6c5157e Mon Sep 17 00:00:00 2001 From: fomys Date: Sun, 28 Apr 2019 16:59:08 +0000 Subject: [PATCH] Initial commit --- Pipfile | 13 + Pipfile.lock | 50 +++ errors.py | 28 ++ main.py | 626 +++++++++++++++++++++++++++++++++++ modules/__init__.py | 0 modules/base/__init__.py | 408 +++++++++++++++++++++++ modules/base/version.json | 10 + modules/errors/__init__.py | 88 +++++ modules/errors/version.json | 13 + modules/modules/__init__.py | 115 +++++++ modules/modules/version.json | 13 + 11 files changed, 1364 insertions(+) create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 errors.py create mode 100644 main.py create mode 100644 modules/__init__.py create mode 100644 modules/base/__init__.py create mode 100644 modules/base/version.json create mode 100644 modules/errors/__init__.py create mode 100644 modules/errors/version.json create mode 100644 modules/modules/__init__.py create mode 100644 modules/modules/version.json diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..6749cf4 --- /dev/null +++ b/Pipfile @@ -0,0 +1,13 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +packaging = "*" +discord-py = {extras = ["voice"],git = "https://github.com/Rapptz/discord.py",ref = "84c1eac62a775a37b03bd0971b221b0c50724630"} + +[dev-packages] + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..80c1dbe --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,50 @@ +{ + "_meta": { + "hash": { + "sha256": "3acaf69a4f810e9def8839c2fb3e6ed5f70b0c37439fecd98c30ac32b0809995" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "discord-py": { + "extras": [ + "voice" + ], + "git": "https://github.com/Rapptz/discord.py", + "ref": "84c1eac62a775a37b03bd0971b221b0c50724630" + }, + "packaging": { + "hashes": [ + "sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af", + "sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3" + ], + "index": "pypi", + "version": "==19.0" + }, + "pyparsing": { + "hashes": [ + "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", + "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03" + ], + "version": "==2.4.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + } + }, + "develop": {} +} diff --git a/errors.py b/errors.py new file mode 100644 index 0000000..6e93abf --- /dev/null +++ b/errors.py @@ -0,0 +1,28 @@ +class LBIException(Exception): + """ + Base exception class for LBI + + All other exceptions are subclasses + """ + pass + + +class ModuleException(LBIException): + """ + Base exception class for all module errors + """ + pass + + +class ModuleNotInstalled(ModuleException): + """ + Raised when a module is not found in module directory + """ + pass + + +class IncompatibleModule(ModuleException): + """ + Raised when a module is not compatible with bot version + """ + pass diff --git a/main.py b/main.py new file mode 100644 index 0000000..8ae5f11 --- /dev/null +++ b/main.py @@ -0,0 +1,626 @@ +#!/usr/bin/python3 + +import importlib +import json +import logging +import logging.config +import os +import traceback +from typing import Dict + +import discord +from packaging.version import Version + +from errors import IncompatibleModule +from modules.base import BaseClass + +__version__ = "0.1.0" + + +class Module: + name: str + + def __init__(self, name: str): + """ + Init module + + :param name: Module name + :type name: str + """ + self.name = name + MODULES.update({self.name: self}) + + @property + def exists(self) -> bool: + """ + Check if module exists + + :return: True if module is present in modules folders + :rtype: bool + """ + if not os.path.isdir(os.path.join("modules", self.name)): + return False + return True + + @property + def complete(self) -> bool: + """ + Check if module is complete + + :return: True if module is compatible + :rtype: Boolean + """ + # Check if version.json exists + if not os.path.exists(os.path.join("modules", self.name, "version.json")): + return False + with open(os.path.join("modules", self.name, "version.json")) as file: + versions = json.load(file) + if "version" not in versions.keys(): + return False + if "dependencies" not in versions.keys(): + return False + if "bot_version" not in versions.keys(): + return False + return True + + @property + def version(self) -> Version: + """ + Returns module version + + :return: current module version + :rtype: Version + """ + with open(os.path.join("modules", self.name, "version.json")) as file: + versions = json.load(file) + return Version(versions["version"]) + + @property + def bot_version(self) -> dict: + """ + returns the min and max version of the bot that is compatible with the module + + :return: Min and max version for bot + :rtype: dict + :raises IncompatibleModule: If bot_version is not properly formated (there must be min and max keys) + """ + with open(os.path.join("modules", self.name, "version.json")) as file: + versions = json.load(file) + try: + return {"min": Version(versions["bot_version"]["min"]), + "max": Version(versions["bot_version"]["max"])} + except KeyError: + raise IncompatibleModule(f"Module {self.name} is not compatible with bot (version.json does not " + f"contain bot_version.max or bot_version.min item)") + + @property + def dependencies(self) -> dict: + """ + return list of dependencies version + + :return: list of dependencies version + :rtype: dict + :raises IncompatibleModule: If bot_version is not properly formated (there must be min and max keys for each + dependencies) + """ + with open(os.path.join("modules", self.name, "version.json")) as file: + versions = json.load(file) + try: + deps = {} + for name, dep in versions["dependencies"].items(): + dep_ver = {"min": Version(dep["min"]), + "max": Version(dep["max"])} + deps.update({name: dep_ver}) + return deps + except KeyError: + raise IncompatibleModule(f"Module {self.name} is not compatible with bot (version.json does not " + f"contain dependencies.modulename.max or dependencies.modulename.min item)") + + @property + def compatible(self) -> bool: + """ + Check if module is compatible with current installation + + :return: True if all dependencies are okays + :rtype: bool + """ + # Check bot version + bot_ver = Version(__version__) + if bot_ver < self.bot_version["min"]: + return False + if bot_ver > self.bot_version["max"]: + return False + for name, dep in self.dependencies.items(): + if name not in MODULES.keys(): + Module(name) + if MODULES[name].version < dep["min"]: + return False + if MODULES[name].version > dep["max"]: + return False + return True + + +MODULES: Dict[str, Module] = {} + + +def setup_logging(default_path='config/log_config.json', default_level=logging.INFO, env_key='LBI_LOG_CONFIG'): + """Setup logging configuration + """ + path = default_path + value = os.getenv(env_key, None) + if value: + path = value + if os.path.exists(path): + with open(path, 'rt') as f: + config = json.load(f) + logging.config.dictConfig(config) + else: + logging.basicConfig(level=default_level) + + +def modules_edit(func): + def wrapper(self, *args, **kwargs): + print(func.__name__, ":", self.reloading) + if self.reloading: + return func(self, *args, **kwargs) + else: + self.reloading = True + a = func(self, *args, **kwargs) + self.reloading = False + return a + + return wrapper + + +def event(func): + def wrapper(self, *args, **kwargs): + if self.reloading: + return lambda: None + else: + return func(self, *args, **kwargs) + + return wrapper + + +setup_logging() + +log_discord = logging.getLogger('discord') +log_LBI = logging.getLogger('LBI') + +debug = log_LBI.debug +info = log_LBI.info +warning = log_LBI.warning +error = log_LBI.error +critical = log_LBI.critical + + +def load_modules_info(): + for mod in os.listdir("modules"): + Module(mod) + + +class LBI(discord.Client): + base_path = "storage" + debug = log_LBI.debug + info = log_LBI.info + warning = log_LBI.warning + error = log_LBI.error + critical = log_LBI.critical + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.reloading = False + self.id = ClientById(self) + self.ready = False + # Content: {"module_name": {"module": imported module, "class": initialized class}} + self.modules = {} + self.config = { + "modules": ["modules"], + "prefix": "%", + } + self.owners = [281166473102098433, 318866596502306816] + self.load_config() + self.load_modules() + + def load_config(self, config_file="config/config.json"): + if os.path.exists(config_file): + with open(config_file, 'rt') as f: + config = json.load(f) + self.config.update(config) + info("Config successfully loaded.") + else: + with open(config_file, 'w') as f: + json.dump(self.config, f) + info("Config successfully created.") + + def save_config(self, config_file="config/config.json"): + with open(config_file, "w") as f: + json.dump(self.config, f) + info("Config successfully saved.") + + @modules_edit + def load_modules(self): + info("Starts to load modules...") + e = {} + for module in self.config["modules"]: + e.update({module: self.load_module(module)}) + info("Finished to load all modules.") + return e + + @modules_edit + def load_module(self, module): + """ + + Status codes: + - 0: Module loaded + - 1: Module not in modules folder + - 2: Module incomplete + - 3: Module incompatible + + :param module: Module name + :return: Status code + """ + + # Check module compatibility + load_modules_info() + if not MODULES.get(module): + return 1 + if not MODULES[module].exists: + return 1 + if not MODULES[module].complete: + return 2 + if not MODULES[module].compatible: + return 3 + deps = MODULES[module].dependencies + for dep in deps.keys(): + if dep not in self.modules.keys(): + self.load_module(dep) + try: + info("Start loading module {module}...".format(module=module)) + imported = importlib.import_module('modules.' + module) + importlib.reload(imported) + initialized_class = imported.MainClass(self) + self.modules.update({module: {"imported": imported, "initialized_class": initialized_class}}) + info("Module {module} successfully imported.".format(module=module)) + initialized_class.on_load() + if module not in self.config["modules"]: + self.config["modules"].append(module) + self.save_config() + except AttributeError as e: + error("Module {module} doesn't have MainClass.".format(module=module)) + return e + return 0 + + @modules_edit + def unload_module(self, module): + info("Start unload module {module}...".format(module=module)) + try: + if module in self.config["modules"]: + self.config["modules"].remove(module) + self.save_config() + self.unload_all() + self.load_modules() + except KeyError as e: + error("Module {module} not loaded.").format(module=module) + return e + + @modules_edit + def reload(self): + del self.modules + self.load_modules() + + @modules_edit + def unload_all(self): + del self.modules + self.modules = {} + + @event + async def on_ready(self): + for module in self.modules.values(): + await module["initialized_class"].on_ready() + + @event + async def on_socket_raw_receive(self, message): + for module in self.modules.values(): + await module["initialized_class"].on_socket_raw_receive(message) + + @event + async def on_socket_raw_send(self, payload): + for module in self.modules.values(): + await module["initialized_class"].on_socket_raw_send(payload) + + @event + async def on_typing(self, channel, user, when): + for module in self.modules.values(): + await module["initialized_class"].on_typing(channel, user, when) + + @event + async def on_message(self, message): + print(message.content) + try: + for module in self.modules.values(): + await module["initialized_class"]._on_message(message) + except RuntimeError: + info("Liste des modules changée pendant l'execution d'un on_message") + + @event + async def on_message_delete(self, message): + for module in self.modules.values(): + await module["initialized_class"].on_message_delete(message) + + @event + async def on_raw_message_delete(self, payload): + for module in self.modules.values(): + await module["initialized_class"].on_raw_message_delete(payload) + + @event + async def on_raw_bulk_message_delete(self, payload): + for module in self.modules.values(): + await module["initialized_class"].on_raw_bulk_message_delete(payload) + + @event + async def on_message_edit(self, before, after): + for module in self.modules.values(): + await module["initialized_class"].on_message_edit(before, after) + + @event + async def on_raw_message_edit(self, payload): + for module in self.modules.values(): + await module["initialized_class"].on_raw_message_edit(payload) + + @event + async def on_reaction_add(self, reaction, user): + for module in self.modules.values(): + await module["initialized_class"].on_reaction_add(reaction, user) + + @event + async def on_raw_reaction_add(self, payload): + for module in self.modules.values(): + await module["initialized_class"].on_raw_reaction_add(payload) + + @event + async def on_reaction_remove(self, reaction, user): + for module in self.modules.values(): + await module["initialized_class"].on_reaction_remove(reaction, user) + + @event + async def on_raw_reaction_remove(self, payload): + for module in self.modules.values(): + await module["initialized_class"].on_raw_reaction_remove(payload) + + @event + async def on_reaction_clear(self, message, reactions): + for module in self.modules.values(): + await module["initialized_class"].on_reaction_clear(message, reactions) + + @event + async def on_raw_reaction_clear(self, payload): + for module in self.modules.values(): + await module["initialized_class"].on_raw_reaction_clear(payload) + + @event + async def on_private_channel_delete(self, channel): + for module in self.modules.values(): + await module["initialized_class"].on_private_channel_delete(channel) + + @event + async def on_private_channel_create(self, channel): + for module in self.modules.values(): + await module["initialized_class"].on_private_channel_create(channel) + + @event + async def on_private_channel_update(self, before, after): + for module in self.modules.values(): + await module["initialized_class"].on_private_channel_update(before, after) + + @event + async def on_private_channel_pins_update(self, channel, last_pin): + for module in self.modules.values(): + await module["initialized_class"].on_private_channel_pins_update(channel, last_pin) + + @event + async def on_guild_channel_delete(self, channel): + for module in self.modules.values(): + await module["initialized_class"].on_guild_channel_delete(channel) + + @event + async def on_guild_channel_create(self, channel): + for module in self.modules.values(): + await module["initialized_class"].on_guild_channel_create(channel) + + @event + async def on_guild_channel_update(self, before, after): + for module in self.modules.values(): + await module["initialized_class"].on_guild_channel_update(before, after) + + @event + async def on_guild_channel_pins_update(self, channel, last_pin): + for module in self.modules.values(): + await module["initialized_class"].on_guild_channel_pins_update(channel, last_pin) + + @event + async def on_member_join(self, member): + for module in self.modules.values(): + await module["initialized_class"].on_member_join(member) + + @event + async def on_member_remove(self, member): + for module in self.modules.values(): + await module["initialized_class"].on_member_remove(member) + + @event + async def on_member_update(self, before, after): + for module in self.modules.values(): + await module["initialized_class"].on_member_update(before, after) + + @event + async def on_guild_join(self, guild): + for module in self.modules.values(): + await module["initialized_class"].on_guild_join(guild) + + @event + async def on_guild_remove(self, guild): + for module in self.modules.values(): + await module["initialized_class"].on_guild_remove(guild) + + @event + async def on_guild_update(self, before, after): + for module in self.modules.values(): + await module["initialized_class"].on_guild_update(before, after) + + @event + async def on_guild_role_create(self, role): + for module in self.modules.values(): + await module["initialized_class"].on_guild_role_create(role) + + @event + async def on_guild_role_delete(self, role): + for module in self.modules.values(): + await module["initialized_class"].on_guild_role_delete(role) + + @event + async def on_guild_role_update(self, before, after): + for module in self.modules.values(): + await module["initialized_class"].on_guild_role_update(before, after) + + @event + async def on_guild_emojis_update(self, guild, before, after): + for module in self.modules.values(): + await module["initialized_class"].on_guild_emojis_update(guild, before, after) + + @event + async def on_guild_available(self, guild): + for module in self.modules.values(): + await module["initialized_class"].on_guild_available(guild) + + @event + async def on_guild_unavailable(self, guild): + for module in self.modules.values(): + await module["initialized_class"].on_guild_unavailable(guild) + + @event + async def on_voice_state_update(self, member, before, after): + for module in self.modules.values(): + await module["initialized_class"].on_voice_state_update(member, before, after) + + @event + async def on_member_ban(self, guild, user): + for module in self.modules.values(): + await module["initialized_class"].on_member_ban(guild, user) + + @event + async def on_member_unban(self, guild, user): + for module in self.modules.values(): + await module["initialized_class"].on_member_unban(guild, user) + + @event + async def on_group_join(self, channel, user): + for module in self.modules.values(): + await module["initialized_class"].on_group_join(channel, user) + + @event + async def on_group_remove(self, channel, user): + for module in self.modules.values(): + await module["initialized_class"].on_group_remove(channel, user) + + @event + async def on_relationship_add(self, relationship): + for module in self.modules.values(): + await module["initialized_class"].on_relationship_add(relationship) + + @event + async def on_relationship_remove(self, relationship): + for module in self.modules.values(): + await module["initialized_class"].on_relationship_remove(relationship) + + @event + async def on_relationship_update(self, before, after): + for module in self.modules.values(): + await module["initialized_class"].on_relationship_update(before, after) + + @event + async def on_connect(self): + for module in self.modules.values(): + await module["initialized_class"].on_connect() + + @event + async def on_shard_ready(self): + for module in self.modules.values(): + await module["initialized_class"].on_shard_ready() + + @event + async def on_resumed(self): + for module in self.modules.values(): + await module["initialized_class"].on_resumed() + + @event + async def on_error(self, event_, *args, **kwargs): + print(event_, *args, **kwargs) + print(traceback.format_exc()) + for module in self.modules.values(): + await module["initialized_class"].on_error(event_, *args, **kwargs) + + @event + async def on_guild_integrations_update(self, guild): + for module in self.modules.values(): + await module["initialized_class"].on_guild_integrations_update(guild) + + @event + async def on_webhooks_update(self, channel): + for module in self.modules.values(): + await module["initialized_class"].on_webhooks_update(channel) + + +class ClientById: + client: LBI + + def __init__(self, client_): + self.client = client_ + + async def fetch_message(self, id_, *args, **kwargs): + """Find a message by id + + :param id_: Id of message to find + :type id_: int + + :raises discord.NotFound: This exception is raised when a message is not found (or not accessible by bot) + + :rtype: discord.Message + :return: discord.Message instance if message is found. + """ + msg = None + for channel in self.client.get_all_channels(): + try: + return await channel.fetch_message(id_, *args, **kwargs) + except discord.NotFound: + continue + if msg is None: + raise discord.NotFound(None, "Message not found") + + async def edit_message(self, id_, *args, **kwargs): + """Edit message by id_ + + :param id_: Id of the message to edit + :type id_: int""" + message = await self.fetch_message(id_) + return await message.edit(**kwargs) + + async def remove_reaction(self, id_message, *args, **kwargs): + """Remove reaction from message by id + + :param id_message: Id of message + :type id_message: int""" + message = await self.fetch_message(id_message) + return await message.remove_reaction(*args, **kwargs) + + async def send_message(self, id_, *args, **kwargs): + """Send message by channel id + + :param id_: Id of channel where to send message + :type id_: int""" + channel = self.client.get_channel(id_) + return channel.send(*args, **kwargs) + + +client = LBI() +client.run('NTUwMDkxOTAyMDY2ODg0NjA4.XKpsPQ.T5emitHQDrt7SxfUNgY1awzX-OY', max_messages=500000) diff --git a/modules/__init__.py b/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/modules/base/__init__.py b/modules/base/__init__.py new file mode 100644 index 0000000..d3ec095 --- /dev/null +++ b/modules/base/__init__.py @@ -0,0 +1,408 @@ +"""Base class for module, never use directly !!!""" +import os +import pickle +import zipfile + +import discord + + +class Storage: + def __init__(self, base_path, client): + self.client = client + self.base_path = base_path + try: + os.makedirs(base_path) + except FileExistsError: + self.client.info("Le dossier {dossier} a déjà été créé.".format(dossier=self.base_path)) + + def mkdir(self, directory): + try: + os.makedirs(self.path(directory)) + except FileExistsError: + self.client.info("Le dossier {dossier} a déjà été créé.".format(dossier=directory)) + + def mkzip(self, files, name): + with zipfile.ZipFile(self.path(files), 'w', zipfile.ZIP_DEFLATED) as zip_file: + for file in files: + zip_file.write(self.path(file), compress_type=zipfile.ZIP_DEFLATED) + return name + + def open(self, filename, *args, **kwargs): + return open(self.path(filename), *args, **kwargs) + + def path(self, filename): + return os.path.join(self.base_path, filename) + + def exists(self, filename): + return os.path.exists(self.path(filename)) + + +class BaseClass: + """Base class for all modules, Override it to make submodules""" + name = "" + help = { + "description": "", + "commands": { + + } + } + help_active = False + color = 0x000000 + command_text = None + authorized_roles = [] + + def __init__(self, client): + """Initialize module class + + Initialize module class, always call it to set self.client when you override it. + + :param client: client instance + :type client: NikolaTesla""" + self.client = client + if not os.path.isdir(os.path.join("storage", self.name)): + os.makedirs(os.path.join("storage", self.name)) + self.storage = Storage(os.path.join(self.client.base_path, self.name), client) + + async def send_help(self, channel): + embed = discord.Embed( + title="[{nom}] - Aide".format(nom=self.name), + description=self.help["description"].format(prefix=self.client.config['prefix']), + color=self.color + ) + for command, description in self.help["commands"].items(): + embed.add_field(name=command.format(prefix=self.client.config['prefix'], command=self.command_text), + value=description.format(prefix=self.client.config['prefix'], command=self.command_text), + inline=False) + await channel.send(embed=embed) + + async def auth(self, user, role_list): + if type(role_list) == list: + if user.id in self.client.owners: + return True + for guild in self.client.guilds: + if guild.get_member(user.id): + for role_id in role_list: + if role_id in [r.id for r in guild.get_member(user.id).roles]: + return True + elif type(role_list) == str: + module_name = role_list + if user.id in self.client.owners: + return True + authorized_roles = self.client.modules[module_name]["class"].authorized_roles + if len(authorized_roles): + for guild in self.client.guilds: + if guild.get_member(user.id): + for role_id in authorized_roles: + if role_id in [r.id for r in guild.get_member(user.id).roles]: + return True + else: + return True + return False + + async def parse_command(self, message): + """Parse a command_text from received message and execute function + %git update + com_update(m..) + Parse message like `{prefix}{command_text} subcommand` and call class method `com_{subcommand}`. + + :param message: message to parse + :type message: discord.Message""" + if message.content.startswith(self.client.config["prefix"] + (self.command_text if self.command_text else "")): + + content = message.content.lstrip( + self.client.config["prefix"] + (self.command_text if self.command_text else "")) + sub_command, args, kwargs = self._parse_command_content(content) + sub_command = "com_" + sub_command + if sub_command in dir(self): + await self.__getattribute__(sub_command)(message, args, kwargs) + else: + await self.command(message, [sub_command[4:]] + args, kwargs) + + @staticmethod + def _parse_command_content(content): + """Parse string + + Parse string like `subcommand argument "argument with spaces" -o -shortwaytopassoncharacteroption --longoption + -o "option with argument"`. You can override this function to change parsing. + + :param content: content to parse + :type content: str + + :return: parsed arguments: [subcommand, [arg1, arg2, ...], [(option1, arg1), (option2, arg2), ...]] + :rtype: list[str, list, list]""" + if not len(content.split()): + return "", [], [] + # Sub_command + sub_command = content.split()[0] + args_ = [] + kwargs = [] + if len(content.split()) > 1: + # Take the other part of command_text + content = content.split(" ", 1)[1].replace("\"", "\"\"") + # Splitting around quotes + quotes = [element.split("\" ") for element in content.split(" \"")] + # Split all sub chains but brute chains and flat the resulting list + args = [item.split() if item[0] != "\"" else [item, ] for sublist in quotes for item in sublist] + # Second plating + args = [item for sublist in args for item in sublist] + # args_ are arguments, kwargs are options with arguments + i = 0 + while i < len(args): + if args[i].startswith("\""): + args_.append(args[i][1:-1]) + elif args[i].startswith("--"): + if i + 1 >= len(args): + kwargs.append((args[i].lstrip("-"), None)) + break + if args[i + 1][0] != "-": + kwargs.append((args[i].lstrip("-"), args[i + 1].strip("\""))) + i += 1 + else: + kwargs.append((args[i].lstrip("-"), None)) + elif args[i].startswith("-"): + if len(args[i]) == 2: + if i + 1 >= len(args): + break + if args[i + 1][0] != "-": + kwargs.append((args[i].lstrip("-"), args[i + 1].strip("\""))) + i += 1 + else: + kwargs.append((args[i].lstrip("-"), None)) + else: + kwargs.extend([(arg, None) for arg in args[i][1:]]) + else: + args_.append(args[i]) + i += 1 + return sub_command, args_, kwargs + + async def _on_message(self, message): + """Override this function to deactivate command_text parsing""" + await self.parse_command(message) + await self.on_message(message) + + async def command(self, message, args, kwargs): + """Override this function to handle all messages starting with `{prefix}{command_text}` + + Function which is executed for all command_text doesn't match with a `com_{subcommand}` function""" + pass + + def save_object(self, object_instance, object_name): + """Save object into pickle file""" + with self.storage.open(object_name, "wb") as f: + pickler = pickle.Pickler(f) + pickler.dump(object_instance) + + def load_object(self, object_name): + """Load object from pickle file""" + if self.save_exists(object_name): + with self.storage.open(object_name, "rb") as f: + unpickler = pickle.Unpickler(f) + return unpickler.load() + + def save_exists(self, object_name): + """Check if pickle file exists""" + return self.storage.exists(object_name) + + def on_load(self): + """This function is called when module is loaded""" + pass + + async def on_socket_raw_receive(self, message): + """Override this function to handle this event.""" + pass + + async def on_socket_raw_send(self, payload): + """Override this function to handle this event.""" + pass + + async def on_typing(self, channel, user, when): + """Override this function to handle this event.""" + pass + + async def on_message(self, message): + """Override this function to handle this event.""" + pass + + async def on_message_delete(self, message): + """Override this function to handle this event.""" + pass + + async def on_raw_message_delete(self, payload): + """Override this function to handle this event.""" + pass + + async def on_raw_bulk_message_delete(self, payload): + """Override this function to handle this event.""" + pass + + async def on_message_edit(self, before, after): + """Override this function to handle this event.""" + pass + + async def on_raw_message_edit(self, payload): + """Override this function to handle this event.""" + pass + + async def on_reaction_add(self, reaction, user): + """Override this function to handle this event.""" + pass + + async def on_raw_reaction_add(self, payload): + """Override this function to handle this event.""" + pass + + async def on_reaction_remove(self, reaction, user): + """Override this function to handle this event.""" + pass + + async def on_raw_reaction_remove(self, payload): + """Override this function to handle this event.""" + pass + + async def on_reaction_clear(self, message, reactions): + """Override this function to handle this event.""" + pass + + async def on_raw_reaction_clear(self, payload): + """Override this function to handle this event.""" + pass + + async def on_private_channel_delete(self, channel): + """Override this function to handle this event.""" + pass + + async def on_private_channel_create(self, channel): + """Override this function to handle this event.""" + pass + + async def on_private_channel_update(self, before, after): + """Override this function to handle this event.""" + pass + + async def on_private_channel_pins_update(self, channel, last_pin): + """Override this function to handle this event.""" + pass + + async def on_guild_channel_delete(self, channel): + """Override this function to handle this event.""" + pass + + async def on_guild_channel_create(self, channel): + """Override this function to handle this event.""" + pass + + async def on_guild_channel_update(self, before, after): + """Override this function to handle this event.""" + pass + + async def on_guild_channel_pins_update(self, channel, last_pin): + """Override this function to handle this event.""" + pass + + async def on_member_join(self, member): + """Override this function to handle this event.""" + pass + + async def on_member_remove(self, member): + """Override this function to handle this event.""" + pass + + async def on_member_update(self, before, after): + """Override this function to handle this event.""" + pass + + async def on_guild_join(self, guild): + """Override this function to handle this event.""" + pass + + async def on_guild_remove(self, guild): + """Override this function to handle this event.""" + pass + + async def on_guild_update(self, before, after): + """Override this function to handle this event.""" + pass + + async def on_guild_role_create(self, role): + """Override this function to handle this event.""" + pass + + async def on_guild_role_delete(self, role): + """Override this function to handle this event.""" + pass + + async def on_guild_role_update(self, before, after): + """Override this function to handle this event.""" + pass + + async def on_guild_emojis_update(self, guild, before, after): + """Override this function to handle this event.""" + pass + + async def on_guild_available(self, guild): + """Override this function to handle this event.""" + pass + + async def on_guild_unavailable(self, guild): + """Override this function to handle this event.""" + pass + + async def on_voice_state_update(self, member, before, after): + """Override this function to handle this event.""" + pass + + async def on_member_ban(self, guild, user): + """Override this function to handle this event.""" + pass + + async def on_member_unban(self, guild, user): + """Override this function to handle this event.""" + pass + + async def on_group_join(self, channel, user): + """Override this function to handle this event.""" + pass + + async def on_group_remove(self, channel, user): + """Override this function to handle this event.""" + pass + + async def on_relationship_add(self, relationship): + """Override this function to handle this event.""" + pass + + async def on_relationship_remove(self, relationship): + """Override this function to handle this event.""" + pass + + async def on_relationship_update(self, before, after): + """Override this function to handle this event.""" + pass + + async def on_ready(self): + """Override this function to handle this event.""" + pass + + async def on_connect(self): + """Override this function to handle this event.""" + pass + + async def on_shard_ready(self): + """Override this function to handle this event.""" + pass + + async def on_resumed(self): + """Override this function to handle this event.""" + pass + + async def on_error(self, event, *args, **kwargs): + """Override this function to handle this event.""" + pass + + async def on_guild_integrations_update(self, guild): + """Override this function to handle this event.""" + pass + + async def on_webhooks_update(self, channel): + """Override this function to handle this event.""" + pass diff --git a/modules/base/version.json b/modules/base/version.json new file mode 100644 index 0000000..b8b6b5e --- /dev/null +++ b/modules/base/version.json @@ -0,0 +1,10 @@ +{ + "version":"0.1.0", + "dependencies": { + + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } +} \ No newline at end of file diff --git a/modules/errors/__init__.py b/modules/errors/__init__.py new file mode 100644 index 0000000..c437b2c --- /dev/null +++ b/modules/errors/__init__.py @@ -0,0 +1,88 @@ +import asyncio +import random +import traceback + +import collections +import discord + +from modules.base import BaseClass + + +class MainClass(BaseClass): + name = "errors" + description = "Error handling" + interactive = True + super_users_list = [431043517217898496] + color = 0xdb1348 + help = { + "description": "Montre toutes les erreurs du bot dans discord.", + "commands": { + "`{prefix}{command}`": "Renvoie une erreur de test.", + } + } + command_text = "unicorn" + + def __init__(self, client): + super().__init__(client) + self.errorsDeque = None + self.development_chan_id = [] + self.memes = [ + "", + ] + self.icon = "" + + async def on_ready(self): + if self.save_exists('errorsDeque'): + self.errorsDeque = self.load_object('errorsDeque') + else: + self.errorsDeque = collections.deque() + for i in range(len(self.errorsDeque)): + try: + messagelst = self.errorsDeque.popleft() + channel = self.client.get_channel(messagelst[0]) + delete_message = await channel.fetch_message(messagelst[1]) + await delete_message.delete() + except: + raise + self.save_object(self.errorsDeque, 'errorsDeque') + + async def command(self, message, args, kwargs): + raise Exception("Si cette erreur apparait, alors tout est normal") + + async def on_error(self, event, *args, **kwargs): + embed = discord.Embed(title="Aïe :/", description="```PYTHON\n{0}```".format(traceback.format_exc()), + color=self.color).set_image(url=random.choice(self.memes)) + message_list = None + try: + message = await args[0].channel.send( + embed=embed.set_footer(text="Ce message va s'autodétruire dans une minute.", icon_url=self.icon)) + message_list = [message.channel.id, message.id] + self.errorsDeque.append(message_list) + except: + try: + message = args[1].channel.send( + embed=embed.set_footer(text="Ce message va s'autodétruire dans une minute.", icon_url=self.icon)) + message_list = [message.channel.id, message.id] + self.errorsDeque.append(message_list) + except: + pass + for chanid in self.development_chan_id: + try: + await self.client.get_channel(chanid).send( + embed=embed.set_footer(text="Ce message ne s'autodétruira pas.", icon_url=self.icon)) + except: + pass + self.save_object(self.errorsDeque, 'errorsDeque') + await asyncio.sleep(60) + try: + channel = self.client.get_channel(message_list[0]) + delete_message = await channel.fetch_message(message_list[1]) + await delete_message.delete() + except: + raise + finally: + try: + self.errorsDeque.remove(message_list) + except ValueError: + pass + self.save_object(self.errorsDeque, 'errorsDeque') diff --git a/modules/errors/version.json b/modules/errors/version.json new file mode 100644 index 0000000..acfacc8 --- /dev/null +++ b/modules/errors/version.json @@ -0,0 +1,13 @@ +{ + "version": "0.1.0", + "dependencies": { + "base": { + "min": "0.1.0", + "max": "0.1.0" + } + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } +} \ No newline at end of file diff --git a/modules/modules/__init__.py b/modules/modules/__init__.py new file mode 100644 index 0000000..02788b9 --- /dev/null +++ b/modules/modules/__init__.py @@ -0,0 +1,115 @@ +import os + +import discord + +from modules.base import BaseClass + + + +class MainClass(BaseClass): + name = "modules" + + command_text = "modules" + color = 0x000000 + help = { + "description": "Manage bot modules.", + "commands": { + "`{prefix}{command} list`": "List of available modules.", + "`{prefix}{command} enable `": "Enable module ``.", + "`{prefix}{command} disable `": "Disable module ``.", + "`{prefix}{command} reload `":"Reload module ``", + "`{prefix}{command} web_list`": "List all available modules from repository", + } + } + + def __init__(self, client): + super().__init__(client) + self.storage.mkdir("modules") + + def get_all_modules(self): + all_items = os.listdir("modules") + modules = [] + for item in all_items: + if item not in ["__init__.py", "base", "__pycache__", "dummy"]: + if os.path.isfile(os.path.join("modules", item)): + modules.append(item[:-3]) + else: + modules.append(item) + return set(modules) + + async def com_enable(self, message, args, kwargs): + args = args[1:] + if len(args) == 0: + await message.channel.send("You must specify at least one module.") + return + if len(args) == 1 and args[0] == "*": + for module in self.get_all_modules(): + e = self.client.load_module(module) + if e: + await message.channel.send("An error occurred during the loading of the module {module}." + .format(module=module)) + await self.com_list(message, args, kwargs) + return + for arg in args: + e = self.client.load_module(arg) + if e: + await message.channel.send("An error occurred during the loading of the module {module}." + .format(module=arg)) + await self.com_list(message, args, kwargs) + + async def com_reload(self, message, args, kwargs): + args = args[1:] + if len(args) == 0: + await message.channel.send("You must specify at least one module.") + return + if len(args) == 1 and args[0] == "*": + for module in self.get_all_modules(): + e = self.client.unload_module(module) + if e: + await message.channel.send("An error occurred during the loading of the module {module}." + .format(module=module)) + await self.com_list(message, args, kwargs) + return + for arg in args: + print(arg) + e = self.client.unload_module(arg) + if e: + await message.channel.send("An error occurred during the loading of the module {module}." + .format(module=arg)) + await self.com_list(message, [], []) + + async def com_disable(self, message, args, kwargs): + args = args[1:] + if len(args) == 0: + await message.channel.send("You must specify at least one module.") + return + if len(args) == 1 and args[0] == "*": + for module in self.get_all_modules(): + e = self.client.unload_module(module) + if e: + await message.channel.send("An error occurred during the loading of the module {module}." + .format(module=module)) + await self.com_list(message, args, kwargs) + return + for arg in args: + print(arg) + e = self.client.unload_module(arg) + if e: + await message.channel.send("An error occurred during the loading of the module {module}." + .format(module=arg)) + await self.com_list(message, [], []) + + async def com_list(self, message, args, kwargs): + list_files = self.get_all_modules() + activated = set(self.client.config["modules"]) + activated_string = "\n+ " + "\n+ ".join(activated) + deactivated_string = "- " + "\n- ".join(list_files.difference(activated)) + embed = discord.Embed(title="[Modules] - Liste des modules", + description="```diff\n{activated}\n{deactivated}```".format( + activated=activated_string, + deactivated=deactivated_string) + ) + await message.channel.send(embed=embed) + + async def com_web_list(self, message, args, kwargs): + pass diff --git a/modules/modules/version.json b/modules/modules/version.json new file mode 100644 index 0000000..acfacc8 --- /dev/null +++ b/modules/modules/version.json @@ -0,0 +1,13 @@ +{ + "version": "0.1.0", + "dependencies": { + "base": { + "min": "0.1.0", + "max": "0.1.0" + } + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } +} \ No newline at end of file