From 0ed718d6d49a8def12dedd19abd45c2517ae2be2 Mon Sep 17 00:00:00 2001 From: Louis Chauvet Date: Tue, 21 Apr 2020 10:46:36 +0200 Subject: [PATCH] Petit checkpoint --- Makefile => doc/Makefile | 0 make.bat => doc/make.bat | 0 {source => doc/source}/api/config.rst | 0 {source => doc/source}/api/main.rst | 0 {source => doc/source}/api/modules.rst | 0 {source => doc/source}/api/utils.emojis.rst | 0 {source => doc/source}/api/utils.rst | 0 {source => doc/source}/conf.py | 0 {source => doc/source}/index.rst | 0 .../source}/module_creation/index.rst | 0 .../source}/module_creation/intro.rst | 0 modules/newmember/version.json | 11 - modules/panic/version.json | 11 - modules/perdu/version.json | 11 - modules/purge/version.json | 11 - modules/readrules/version.json | 11 - modules/restart/version.json | 11 - modules/roles/version.json | 11 - modules/rtfgd/version.json | 11 - {modules => src/bot_base}/__init__.py | 0 .../roles.py => src/bot_base/bot_base.py | 0 {config => src/config}/__init__.py | 0 {config => src/config}/base.py | 0 .../config}/config_types/__init__.py | 0 .../config}/config_types/base_type.py | 0 {config => src/config}/config_types/bool.py | 0 {config => src/config}/config_types/color.py | 0 {config => src/config}/config_types/dict.py | 0 .../config_types/discord_types/__init__.py | 0 .../config_types/discord_types/channel.py | 0 .../config_types/discord_types/guild.py | 0 .../config_types/discord_types/role.py | 0 .../config_types/discord_types/user.py | 0 {config => src/config}/config_types/float.py | 0 {config => src/config}/config_types/int.py | 0 {config => src/config}/config_types/list.py | 0 {config => src/config}/config_types/str.py | 0 {config => src/config}/log_config.json | 0 errors.py => src/errors.py | 56 +- main.py => src/main.py | 998 +++++++++--------- {utils => src/modules}/__init__.py | 0 {modules => src/modules}/avalon/__init__.py | 0 src/modules/avalon/roles.py | 0 {modules => src/modules}/avalon/version.json | 20 +- {modules => src/modules}/base/__init__.py | 4 +- .../base/Base.py => src/modules/base/base.py | 7 +- .../modules/base/base_lua.py | 2 +- .../modules/base/base_python.py | 2 +- {modules => src/modules}/base/version.json | 20 +- {modules => src/modules}/clean/__init__.py | 0 {modules => src/modules}/clean/version.json | 20 +- {modules => src/modules}/errors/__init__.py | 0 .../modules/errors}/version.json | 26 +- {modules => src/modules}/help/__init__.py | 0 {modules => src/modules}/help/version.json | 20 +- {modules => src/modules}/modules/__init__.py | 282 ++--- {modules => src/modules}/modules/api.py | 0 .../modules/modules}/version.json | 26 +- .../modules}/newmember/__init__.py | 0 src/modules/newmember/version.json | 11 + {modules => src/modules}/panic/__init__.py | 0 src/modules/panic/version.json | 11 + {modules => src/modules}/perdu/__init__.py | 0 src/modules/perdu/version.json | 11 + {modules => src/modules}/purge/__init__.py | 0 src/modules/purge/version.json | 11 + .../modules}/readrules/__init__.py | 0 src/modules/readrules/version.json | 11 + {modules => src/modules}/restart/__init__.py | 0 src/modules/restart/version.json | 11 + {modules => src/modules}/roles/__init__.py | 0 src/modules/roles/version.json | 11 + {modules => src/modules}/rtfgd/__init__.py | 0 src/modules/rtfgd/version.json | 11 + pytest.ini => src/pytest.ini | 0 {storage => src/storage}/__init__.py | 0 {storage => src/storage}/jsonencoder.py | 0 {storage => src/storage}/objects.py | 0 src/utils/__init__.py | 0 {utils => src/utils}/emojis.py | 0 80 files changed, 830 insertions(+), 829 deletions(-) rename Makefile => doc/Makefile (100%) rename make.bat => doc/make.bat (100%) rename {source => doc/source}/api/config.rst (100%) rename {source => doc/source}/api/main.rst (100%) rename {source => doc/source}/api/modules.rst (100%) rename {source => doc/source}/api/utils.emojis.rst (100%) rename {source => doc/source}/api/utils.rst (100%) rename {source => doc/source}/conf.py (100%) rename {source => doc/source}/index.rst (100%) rename {source => doc/source}/module_creation/index.rst (100%) rename {source => doc/source}/module_creation/intro.rst (100%) delete mode 100644 modules/newmember/version.json delete mode 100644 modules/panic/version.json delete mode 100644 modules/perdu/version.json delete mode 100644 modules/purge/version.json delete mode 100644 modules/readrules/version.json delete mode 100644 modules/restart/version.json delete mode 100644 modules/roles/version.json delete mode 100644 modules/rtfgd/version.json rename {modules => src/bot_base}/__init__.py (100%) rename modules/avalon/roles.py => src/bot_base/bot_base.py (100%) rename {config => src/config}/__init__.py (100%) rename {config => src/config}/base.py (100%) rename {config => src/config}/config_types/__init__.py (100%) rename {config => src/config}/config_types/base_type.py (100%) rename {config => src/config}/config_types/bool.py (100%) rename {config => src/config}/config_types/color.py (100%) rename {config => src/config}/config_types/dict.py (100%) rename {config => src/config}/config_types/discord_types/__init__.py (100%) rename {config => src/config}/config_types/discord_types/channel.py (100%) rename {config => src/config}/config_types/discord_types/guild.py (100%) rename {config => src/config}/config_types/discord_types/role.py (100%) rename {config => src/config}/config_types/discord_types/user.py (100%) rename {config => src/config}/config_types/float.py (100%) rename {config => src/config}/config_types/int.py (100%) rename {config => src/config}/config_types/list.py (100%) rename {config => src/config}/config_types/str.py (100%) rename {config => src/config}/log_config.json (100%) rename errors.py => src/errors.py (94%) rename main.py => src/main.py (96%) rename {utils => src/modules}/__init__.py (100%) rename {modules => src/modules}/avalon/__init__.py (100%) create mode 100644 src/modules/avalon/roles.py rename {modules => src/modules}/avalon/version.json (92%) rename {modules => src/modules}/base/__init__.py (98%) rename modules/base/Base.py => src/modules/base/base.py (98%) rename modules/base/BaseLua.py => src/modules/base/base_lua.py (98%) rename modules/base/BasePython.py => src/modules/base/base_python.py (80%) rename {modules => src/modules}/base/version.json (92%) rename {modules => src/modules}/clean/__init__.py (100%) rename {modules => src/modules}/clean/version.json (92%) rename {modules => src/modules}/errors/__init__.py (100%) rename {modules/modules => src/modules/errors}/version.json (93%) rename {modules => src/modules}/help/__init__.py (100%) rename {modules => src/modules}/help/version.json (92%) rename {modules => src/modules}/modules/__init__.py (97%) rename {modules => src/modules}/modules/api.py (100%) rename {modules/errors => src/modules/modules}/version.json (93%) rename {modules => src/modules}/newmember/__init__.py (100%) create mode 100644 src/modules/newmember/version.json rename {modules => src/modules}/panic/__init__.py (100%) create mode 100644 src/modules/panic/version.json rename {modules => src/modules}/perdu/__init__.py (100%) create mode 100644 src/modules/perdu/version.json rename {modules => src/modules}/purge/__init__.py (100%) create mode 100644 src/modules/purge/version.json rename {modules => src/modules}/readrules/__init__.py (100%) create mode 100644 src/modules/readrules/version.json rename {modules => src/modules}/restart/__init__.py (100%) create mode 100644 src/modules/restart/version.json rename {modules => src/modules}/roles/__init__.py (100%) create mode 100644 src/modules/roles/version.json rename {modules => src/modules}/rtfgd/__init__.py (100%) create mode 100644 src/modules/rtfgd/version.json rename pytest.ini => src/pytest.ini (100%) rename {storage => src/storage}/__init__.py (100%) rename {storage => src/storage}/jsonencoder.py (100%) rename {storage => src/storage}/objects.py (100%) create mode 100644 src/utils/__init__.py rename {utils => src/utils}/emojis.py (100%) diff --git a/Makefile b/doc/Makefile similarity index 100% rename from Makefile rename to doc/Makefile diff --git a/make.bat b/doc/make.bat similarity index 100% rename from make.bat rename to doc/make.bat diff --git a/source/api/config.rst b/doc/source/api/config.rst similarity index 100% rename from source/api/config.rst rename to doc/source/api/config.rst diff --git a/source/api/main.rst b/doc/source/api/main.rst similarity index 100% rename from source/api/main.rst rename to doc/source/api/main.rst diff --git a/source/api/modules.rst b/doc/source/api/modules.rst similarity index 100% rename from source/api/modules.rst rename to doc/source/api/modules.rst diff --git a/source/api/utils.emojis.rst b/doc/source/api/utils.emojis.rst similarity index 100% rename from source/api/utils.emojis.rst rename to doc/source/api/utils.emojis.rst diff --git a/source/api/utils.rst b/doc/source/api/utils.rst similarity index 100% rename from source/api/utils.rst rename to doc/source/api/utils.rst diff --git a/source/conf.py b/doc/source/conf.py similarity index 100% rename from source/conf.py rename to doc/source/conf.py diff --git a/source/index.rst b/doc/source/index.rst similarity index 100% rename from source/index.rst rename to doc/source/index.rst diff --git a/source/module_creation/index.rst b/doc/source/module_creation/index.rst similarity index 100% rename from source/module_creation/index.rst rename to doc/source/module_creation/index.rst diff --git a/source/module_creation/intro.rst b/doc/source/module_creation/intro.rst similarity index 100% rename from source/module_creation/intro.rst rename to doc/source/module_creation/intro.rst diff --git a/modules/newmember/version.json b/modules/newmember/version.json deleted file mode 100644 index b27fd9a..0000000 --- a/modules/newmember/version.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version":"0.1.0", - "type": "python", - "dependencies": { - - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } -} \ No newline at end of file diff --git a/modules/panic/version.json b/modules/panic/version.json deleted file mode 100644 index b27fd9a..0000000 --- a/modules/panic/version.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version":"0.1.0", - "type": "python", - "dependencies": { - - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } -} \ No newline at end of file diff --git a/modules/perdu/version.json b/modules/perdu/version.json deleted file mode 100644 index b27fd9a..0000000 --- a/modules/perdu/version.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version":"0.1.0", - "type": "python", - "dependencies": { - - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } -} \ No newline at end of file diff --git a/modules/purge/version.json b/modules/purge/version.json deleted file mode 100644 index b27fd9a..0000000 --- a/modules/purge/version.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version":"0.1.0", - "type": "python", - "dependencies": { - - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } -} \ No newline at end of file diff --git a/modules/readrules/version.json b/modules/readrules/version.json deleted file mode 100644 index b27fd9a..0000000 --- a/modules/readrules/version.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version":"0.1.0", - "type": "python", - "dependencies": { - - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } -} \ No newline at end of file diff --git a/modules/restart/version.json b/modules/restart/version.json deleted file mode 100644 index b27fd9a..0000000 --- a/modules/restart/version.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version":"0.1.0", - "type": "python", - "dependencies": { - - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } -} \ No newline at end of file diff --git a/modules/roles/version.json b/modules/roles/version.json deleted file mode 100644 index b27fd9a..0000000 --- a/modules/roles/version.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version":"0.1.0", - "type": "python", - "dependencies": { - - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } -} \ No newline at end of file diff --git a/modules/rtfgd/version.json b/modules/rtfgd/version.json deleted file mode 100644 index b27fd9a..0000000 --- a/modules/rtfgd/version.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version":"0.1.0", - "type": "python", - "dependencies": { - - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } -} \ No newline at end of file diff --git a/modules/__init__.py b/src/bot_base/__init__.py similarity index 100% rename from modules/__init__.py rename to src/bot_base/__init__.py diff --git a/modules/avalon/roles.py b/src/bot_base/bot_base.py similarity index 100% rename from modules/avalon/roles.py rename to src/bot_base/bot_base.py diff --git a/config/__init__.py b/src/config/__init__.py similarity index 100% rename from config/__init__.py rename to src/config/__init__.py diff --git a/config/base.py b/src/config/base.py similarity index 100% rename from config/base.py rename to src/config/base.py diff --git a/config/config_types/__init__.py b/src/config/config_types/__init__.py similarity index 100% rename from config/config_types/__init__.py rename to src/config/config_types/__init__.py diff --git a/config/config_types/base_type.py b/src/config/config_types/base_type.py similarity index 100% rename from config/config_types/base_type.py rename to src/config/config_types/base_type.py diff --git a/config/config_types/bool.py b/src/config/config_types/bool.py similarity index 100% rename from config/config_types/bool.py rename to src/config/config_types/bool.py diff --git a/config/config_types/color.py b/src/config/config_types/color.py similarity index 100% rename from config/config_types/color.py rename to src/config/config_types/color.py diff --git a/config/config_types/dict.py b/src/config/config_types/dict.py similarity index 100% rename from config/config_types/dict.py rename to src/config/config_types/dict.py diff --git a/config/config_types/discord_types/__init__.py b/src/config/config_types/discord_types/__init__.py similarity index 100% rename from config/config_types/discord_types/__init__.py rename to src/config/config_types/discord_types/__init__.py diff --git a/config/config_types/discord_types/channel.py b/src/config/config_types/discord_types/channel.py similarity index 100% rename from config/config_types/discord_types/channel.py rename to src/config/config_types/discord_types/channel.py diff --git a/config/config_types/discord_types/guild.py b/src/config/config_types/discord_types/guild.py similarity index 100% rename from config/config_types/discord_types/guild.py rename to src/config/config_types/discord_types/guild.py diff --git a/config/config_types/discord_types/role.py b/src/config/config_types/discord_types/role.py similarity index 100% rename from config/config_types/discord_types/role.py rename to src/config/config_types/discord_types/role.py diff --git a/config/config_types/discord_types/user.py b/src/config/config_types/discord_types/user.py similarity index 100% rename from config/config_types/discord_types/user.py rename to src/config/config_types/discord_types/user.py diff --git a/config/config_types/float.py b/src/config/config_types/float.py similarity index 100% rename from config/config_types/float.py rename to src/config/config_types/float.py diff --git a/config/config_types/int.py b/src/config/config_types/int.py similarity index 100% rename from config/config_types/int.py rename to src/config/config_types/int.py diff --git a/config/config_types/list.py b/src/config/config_types/list.py similarity index 100% rename from config/config_types/list.py rename to src/config/config_types/list.py diff --git a/config/config_types/str.py b/src/config/config_types/str.py similarity index 100% rename from config/config_types/str.py rename to src/config/config_types/str.py diff --git a/config/log_config.json b/src/config/log_config.json similarity index 100% rename from config/log_config.json rename to src/config/log_config.json diff --git a/errors.py b/src/errors.py similarity index 94% rename from errors.py rename to src/errors.py index 6e93abf..4684fb1 100644 --- a/errors.py +++ b/src/errors.py @@ -1,28 +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 +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/src/main.py similarity index 96% rename from main.py rename to src/main.py index 7fb32aa..8d9ee44 100644 --- a/main.py +++ b/src/main.py @@ -1,499 +1,499 @@ -#!/usr/bin/python3 -from __future__ import annotations - -import asyncio -import importlib -import json -import locale -import logging -import logging.config -import os -import sys -import traceback -from typing import Dict - -import discord -import humanize -from packaging.version import Version - -from config import Config, config_types -from config.config_types import factory -from errors import IncompatibleModule -from modules.base import base_supported_type - -__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 type(self) -> str: - """ - Return module type. It can be python or lua - - :return: Module type - :rtype: str - """ - if not os.path.exists(os.path.join("modules", self.name, "version.json")): - return "" - with open(os.path.join("modules", self.name, "version.json")) as file: - versions = json.load(file) - return versions["type"] - - @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 - if "type" not in versions.keys(): - return False - if versions["type"] not in base_supported_type: - 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 - - :raise IncompatibleModule: If bot_version is not properly formated (there must be min and max keys for each dependencies) - :return: list of dependencies version - :rtype: dict - """ - 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): - 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 - - -"""def async_event(func): - async 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_types') -log_LBI = logging.getLogger('LBI') -log_communication = logging.getLogger('communication') - - -def load_modules_info(): - for mod in os.listdir("modules"): - Module(mod) - - -class LBI(discord.Client): - by_id: ClientById - base_path = "data" - debug = log_LBI.debug - info = log_LBI.info - warning = log_LBI.warning - warn = warning - error = log_LBI.error - critical = log_LBI.critical - - def __init__(self, config: Config = None, *args, **kwargs): - super().__init__(*args, **kwargs) - if config is None: - config = Config(path="data/config.toml", client=self) - self.reloading = False - self.by_id = ClientById(self) - self.ready = False - # Content: {"module_name": {"module": imported module, "class": initialized class}} - self.modules = {} - - self.config = config - self.config.register("modules", factory(config_types.List, factory(config_types.Str))) - self.config.register("prefix", factory(config_types.Str)) - self.config.register("admin_roles", factory(config_types.List, factory(config_types.discord_types.Role, self))) - self.config.register("admin_users", factory(config_types.List, factory(config_types.discord_types.User, self))) - self.config.register("main_guild", factory(config_types.discord_types.Guild, self)) - self.config.register("locale", factory(config_types.Str)) - - self.config.set({ - "modules": ["modules", "errors"], - "prefix": "%", - "admin_roles": [], - "admin_users": [], - "main_guild": None, - "locale": "fr_FR.UTF8", - }) - - locale.setlocale(locale.LC_TIME, self.config['locale']) - humanize.i18n.activate(self.config['locale']) - self.load_modules() - - @modules_edit - def load_modules(self): - self.info("Starts to load modules...") - e = {} - for module in self.config["modules"]: - e.update({module: self.load_module(module)}) - self.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(): - if dep != "base": - self.load_module(dep) - if MODULES[module].type == "python": - try: - self.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}}) - self.info("Module {module} successfully imported.".format(module=module)) - initialized_class.dispatch("load") - - if module not in self.config["modules"]: - self.config["modules"].append(module) - self.config.save() - except AttributeError as e: - self.error("Module {module} doesn't have MainClass.".format(module=module)) - raise e - return 0 - elif MODULES[module].type == "lua": - self.info(f"Start loading module {module}...") - imported = importlib.import_module('modules.base.BaseLua') - importlib.reload(imported) - initialized_class = imported.BaseClassLua(self, path=f"modules/{module}/main") - self.modules.update({module: {"imported": imported, "initialized_class": initialized_class}}) - self.info(f"Module {module} successfully imported.") - initialized_class.dispatch("load") - if module not in self.config["modules"]: - self.config["modules"].append(module) - self.config.save() - return 0 - - @modules_edit - def unload_module(self, module): - self.info("Start unload module {module}...".format(module=module)) - try: - if module in self.config["modules"]: - self.config["modules"].remove(module) - self.config.save() - self.unload_all() - self.load_modules() - except KeyError as e: - self.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 - def dispatch(self, event, *args, **kwargs): - # Dispatch to handle wait_* commands - super().dispatch(event, *args, **kwargs) - # Dispatch to modules - for module in self.modules.values(): - module["initialized_class"].dispatch(event, *args, **kwargs) - - async def on_error(self, event_method, *args, **kwargs): - """Function called when error happend""" - # This event is special because it is call directly - self.error(traceback.format_exc()) - for module in self.modules.values(): - await module["initialized_class"].on_error(event_method, *args, **kwargs) - - -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_types.NotFound: This exception is raised when a message is not found (or not accessible by bot) - - :rtype: discord.Message - :return: discord_types.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) - - def get_role(self, id_=None, name=None, check=None, guilds=None): - """Get role by id or with custom check""" - if guilds is None: - guilds = self.client.guilds - if id_ is not None: - for guild in guilds: - role = discord.utils.get(guild.roles, id=id_) - if role: - return role - if name is not None: - for guild in guilds: - role = discord.utils.get(guild.roles, name=name) - if role: - return role - if check is not None: - role = None - for guild in guilds: - for role_ in guild.roles: - if check(role_): - role = role_ - break - if role is not None: - break - return role - return None - - - - -class Communication(asyncio.Protocol): - debug = log_communication.debug - info = log_communication.info - warning = log_communication.warning - error = log_communication.error - critical = log_communication.critical - name = "Communication" - - def __init__(self, client): - self.client = client - self.transport = None - - def connection_made(self, transport): - print('%s: connection made' % self.name) - self.transport = transport - - def data_received(self, data): - print('%s: data received: %r' % (self.name, data)) - - def eof_received(self): - pass - - def connection_lost(self, exc): - print('%s: connection lost: %s' % (self.name, exc)) - -if __name__ == "__main__": - client1 = LBI(max_messages=500000) - communication = Communication(client1) - - - async def start_bot(): - await client1.start(os.environ.get("DISCORD_TOKEN")) - - - print(os.path.join("/tmp", os.path.dirname(os.path.realpath(__file__))) + ".sock") - - loop = asyncio.get_event_loop() - t = loop.create_unix_server(Communication, - path=os.path.join("/tmp", os.path.dirname(os.path.realpath(__file__)) + ".sock")) - if not sys.platform == "win32": - loop.run_until_complete(t) - - loop.create_task(start_bot()) - loop.run_forever() +#!/usr/bin/python3 +from __future__ import annotations + +import asyncio +import importlib +import json +import locale +import logging +import logging.config +import os +import sys +import traceback +from typing import Dict + +import discord +import humanize +from packaging.version import Version + +from config import Config, config_types +from config.config_types import factory +from errors import IncompatibleModule +from modules.base import base_supported_type + +__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 type(self) -> str: + """ + Return module type. It can be python or lua + + :return: Module type + :rtype: str + """ + if not os.path.exists(os.path.join("modules", self.name, "version.json")): + return "" + with open(os.path.join("modules", self.name, "version.json")) as file: + versions = json.load(file) + return versions["type"] + + @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 + if "type" not in versions.keys(): + return False + if versions["type"] not in base_supported_type: + 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 + + :raise IncompatibleModule: If bot_version is not properly formated (there must be min and max keys for each dependencies) + :return: list of dependencies version + :rtype: dict + """ + 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): + 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 + + +"""def async_event(func): + async 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_types') +log_LBI = logging.getLogger('LBI') +log_communication = logging.getLogger('communication') + + +def load_modules_info(): + for mod in os.listdir("modules"): + Module(mod) + + +class LBI(discord.Client): + by_id: ClientById + base_path = "data" + debug = log_LBI.debug + info = log_LBI.info + warning = log_LBI.warning + warn = warning + error = log_LBI.error + critical = log_LBI.critical + + def __init__(self, config: Config = None, *args, **kwargs): + super().__init__(*args, **kwargs) + if config is None: + config = Config(path="data/config.toml", client=self) + self.reloading = False + self.by_id = ClientById(self) + self.ready = False + # Content: {"module_name": {"module": imported module, "class": initialized class}} + self.modules = {} + + self.config = config + self.config.register("modules", factory(config_types.List, factory(config_types.Str))) + self.config.register("prefix", factory(config_types.Str)) + self.config.register("admin_roles", factory(config_types.List, factory(config_types.discord_types.Role, self))) + self.config.register("admin_users", factory(config_types.List, factory(config_types.discord_types.User, self))) + self.config.register("main_guild", factory(config_types.discord_types.Guild, self)) + self.config.register("locale", factory(config_types.Str)) + + self.config.set({ + "modules": ["modules", "errors"], + "prefix": "%", + "admin_roles": [], + "admin_users": [], + "main_guild": None, + "locale": "fr_FR.UTF8", + }) + + locale.setlocale(locale.LC_TIME, self.config['locale']) + humanize.i18n.activate(self.config['locale']) + self.load_modules() + + @modules_edit + def load_modules(self): + self.info("Starts to load modules...") + e = {} + for module in self.config["modules"]: + e.update({module: self.load_module(module)}) + self.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(): + if dep != "base": + self.load_module(dep) + if MODULES[module].type == "python": + try: + self.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}}) + self.info("Module {module} successfully imported.".format(module=module)) + initialized_class.dispatch("load") + + if module not in self.config["modules"]: + self.config["modules"].append(module) + self.config.save() + except AttributeError as e: + self.error("Module {module} doesn't have MainClass.".format(module=module)) + raise e + return 0 + elif MODULES[module].type == "lua": + self.info(f"Start loading module {module}...") + imported = importlib.import_module('modules.base.BaseLua') + importlib.reload(imported) + initialized_class = imported.BaseClassLua(self, path=f"modules/{module}/main") + self.modules.update({module: {"imported": imported, "initialized_class": initialized_class}}) + self.info(f"Module {module} successfully imported.") + initialized_class.dispatch("load") + if module not in self.config["modules"]: + self.config["modules"].append(module) + self.config.save() + return 0 + + @modules_edit + def unload_module(self, module): + self.info("Start unload module {module}...".format(module=module)) + try: + if module in self.config["modules"]: + self.config["modules"].remove(module) + self.config.save() + self.unload_all() + self.load_modules() + except KeyError as e: + self.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 + def dispatch(self, event, *args, **kwargs): + # Dispatch to handle wait_* commands + super().dispatch(event, *args, **kwargs) + # Dispatch to modules + for module in self.modules.values(): + module["initialized_class"].dispatch(event, *args, **kwargs) + + async def on_error(self, event_method, *args, **kwargs): + """Function called when error happend""" + # This event is special because it is call directly + self.error(traceback.format_exc()) + for module in self.modules.values(): + await module["initialized_class"].on_error(event_method, *args, **kwargs) + + +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_types.NotFound: This exception is raised when a message is not found (or not accessible by bot) + + :rtype: discord.Message + :return: discord_types.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) + + def get_role(self, id_=None, name=None, check=None, guilds=None): + """Get role by id or with custom check""" + if guilds is None: + guilds = self.client.guilds + if id_ is not None: + for guild in guilds: + role = discord.utils.get(guild.roles, id=id_) + if role: + return role + if name is not None: + for guild in guilds: + role = discord.utils.get(guild.roles, name=name) + if role: + return role + if check is not None: + role = None + for guild in guilds: + for role_ in guild.roles: + if check(role_): + role = role_ + break + if role is not None: + break + return role + return None + + + + +class Communication(asyncio.Protocol): + debug = log_communication.debug + info = log_communication.info + warning = log_communication.warning + error = log_communication.error + critical = log_communication.critical + name = "Communication" + + def __init__(self, client): + self.client = client + self.transport = None + + def connection_made(self, transport): + print('%s: connection made' % self.name) + self.transport = transport + + def data_received(self, data): + print('%s: data received: %r' % (self.name, data)) + + def eof_received(self): + pass + + def connection_lost(self, exc): + print('%s: connection lost: %s' % (self.name, exc)) + +if __name__ == "__main__": + client1 = LBI(max_messages=500000) + communication = Communication(client1) + + + async def start_bot(): + await client1.start(os.environ.get("DISCORD_TOKEN")) + + + print(os.path.join("/tmp", os.path.dirname(os.path.realpath(__file__))) + ".sock") + + loop = asyncio.get_event_loop() + t = loop.create_unix_server(Communication, + path=os.path.join("/tmp", os.path.dirname(os.path.realpath(__file__)) + ".sock")) + if not sys.platform == "win32": + loop.run_until_complete(t) + + loop.create_task(start_bot()) + loop.run_forever() diff --git a/utils/__init__.py b/src/modules/__init__.py similarity index 100% rename from utils/__init__.py rename to src/modules/__init__.py diff --git a/modules/avalon/__init__.py b/src/modules/avalon/__init__.py similarity index 100% rename from modules/avalon/__init__.py rename to src/modules/avalon/__init__.py diff --git a/src/modules/avalon/roles.py b/src/modules/avalon/roles.py new file mode 100644 index 0000000..e69de29 diff --git a/modules/avalon/version.json b/src/modules/avalon/version.json similarity index 92% rename from modules/avalon/version.json rename to src/modules/avalon/version.json index b27fd9a..53fa16b 100644 --- a/modules/avalon/version.json +++ b/src/modules/avalon/version.json @@ -1,11 +1,11 @@ -{ - "version":"0.1.0", - "type": "python", - "dependencies": { - - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } +{ + "version":"0.1.0", + "type": "python", + "dependencies": { + + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } } \ No newline at end of file diff --git a/modules/base/__init__.py b/src/modules/base/__init__.py similarity index 98% rename from modules/base/__init__.py rename to src/modules/base/__init__.py index e5d1805..2e94b77 100644 --- a/modules/base/__init__.py +++ b/src/modules/base/__init__.py @@ -1,3 +1,3 @@ -from .BasePython import BaseClassPython -from .BaseLua import BaseClassLua +from .BasePython import BaseClassPython +from .BaseLua import BaseClassLua base_supported_type = ["python", "lua"] \ No newline at end of file diff --git a/modules/base/Base.py b/src/modules/base/base.py similarity index 98% rename from modules/base/Base.py rename to src/modules/base/base.py index c63bf32..d419638 100644 --- a/modules/base/Base.py +++ b/src/modules/base/base.py @@ -5,7 +5,8 @@ from typing import List, Union, Optional import discord -from config import Config, config_types +from config import Config +from config import config_types from config.config_types import factory from storage import Objects from utils import emojis @@ -35,9 +36,9 @@ class BaseClass: self.config.register("color", factory(config_types.Color)) self.config.register("auth_everyone", factory(config_types.Bool)) self.config.register("authorized_roles", - factory(config_types.List, factory(config_types.discord_types.Role, client))) + factory(config_types.List, factory(config.config_types.discord_types.Role, client))) self.config.register("authorized_users", - factory(config_types.List, factory(config_types.discord_types.User, client))) + factory(config_types.List, factory(config.config_types.discord_types.User, client))) self.config.register("command_text", factory(config_types.Str)) self.config.set({"help_active": True, "color": 0x000000, diff --git a/modules/base/BaseLua.py b/src/modules/base/base_lua.py similarity index 98% rename from modules/base/BaseLua.py rename to src/modules/base/base_lua.py index 51c57cd..c5ce17d 100644 --- a/modules/base/BaseLua.py +++ b/src/modules/base/base_lua.py @@ -4,7 +4,7 @@ import asyncio import discord import lupa -from modules.base.Base import BaseClass +from modules import BaseClass class BaseClassLua(BaseClass): diff --git a/modules/base/BasePython.py b/src/modules/base/base_python.py similarity index 80% rename from modules/base/BasePython.py rename to src/modules/base/base_python.py index fbb52a8..5cb7d7f 100644 --- a/modules/base/BasePython.py +++ b/src/modules/base/base_python.py @@ -1,6 +1,6 @@ """Base class for module, never use directly !!!""" -from modules.base.Base import BaseClass +from .Base import BaseClass class BaseClassPython(BaseClass): diff --git a/modules/base/version.json b/src/modules/base/version.json similarity index 92% rename from modules/base/version.json rename to src/modules/base/version.json index b27fd9a..53fa16b 100644 --- a/modules/base/version.json +++ b/src/modules/base/version.json @@ -1,11 +1,11 @@ -{ - "version":"0.1.0", - "type": "python", - "dependencies": { - - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } +{ + "version":"0.1.0", + "type": "python", + "dependencies": { + + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } } \ No newline at end of file diff --git a/modules/clean/__init__.py b/src/modules/clean/__init__.py similarity index 100% rename from modules/clean/__init__.py rename to src/modules/clean/__init__.py diff --git a/modules/clean/version.json b/src/modules/clean/version.json similarity index 92% rename from modules/clean/version.json rename to src/modules/clean/version.json index b27fd9a..53fa16b 100644 --- a/modules/clean/version.json +++ b/src/modules/clean/version.json @@ -1,11 +1,11 @@ -{ - "version":"0.1.0", - "type": "python", - "dependencies": { - - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } +{ + "version":"0.1.0", + "type": "python", + "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/src/modules/errors/__init__.py similarity index 100% rename from modules/errors/__init__.py rename to src/modules/errors/__init__.py diff --git a/modules/modules/version.json b/src/modules/errors/version.json similarity index 93% rename from modules/modules/version.json rename to src/modules/errors/version.json index 4cb3a98..85a57f9 100644 --- a/modules/modules/version.json +++ b/src/modules/errors/version.json @@ -1,14 +1,14 @@ -{ - "version": "0.1.0", - "type": "python", - "dependencies": { - "base": { - "min": "0.1.0", - "max": "0.1.0" - } - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } +{ + "version": "0.1.0", + "type": "python", + "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/help/__init__.py b/src/modules/help/__init__.py similarity index 100% rename from modules/help/__init__.py rename to src/modules/help/__init__.py diff --git a/modules/help/version.json b/src/modules/help/version.json similarity index 92% rename from modules/help/version.json rename to src/modules/help/version.json index b27fd9a..53fa16b 100644 --- a/modules/help/version.json +++ b/src/modules/help/version.json @@ -1,11 +1,11 @@ -{ - "version":"0.1.0", - "type": "python", - "dependencies": { - - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } +{ + "version":"0.1.0", + "type": "python", + "dependencies": { + + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } } \ No newline at end of file diff --git a/modules/modules/__init__.py b/src/modules/modules/__init__.py similarity index 97% rename from modules/modules/__init__.py rename to src/modules/modules/__init__.py index 31144c1..e5b646f 100644 --- a/modules/modules/__init__.py +++ b/src/modules/modules/__init__.py @@ -1,141 +1,141 @@ -import os - -import discord -from aiohttp import ClientConnectorError - -from modules.base import BaseClassPython -from modules.modules.api import Api - - -class MainClass(BaseClassPython): - name = "modules" - 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", - # "`{prefix}{command} web_source`": "List all source repositories", - # "`{prefix}{command} web_source remove `": "Remove url from repository list", - # "`{prefix}{command} web_source add `": "Add url to repository list", - } - } - - def __init__(self, client): - super().__init__(client) - os.makedirs("modules", exist_ok=True) - self.api = Api() - - @staticmethod - def get_all_modules(): - all_items = os.listdir("modules") - modules = [] - for item in all_items: - if item not in ["__init__.py", "base", "__pycache__"]: - 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 == 1: - await message.channel.send(f"Module {arg} not exists.") - if e == 2: - await message.channel.send(f"Module {arg} is incompatible.") - elif e: - await message.channel.send(f"An error occurred during the loading of the module {arg}: {e}.") - 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(f"An error occurred during the unloading of the module {module}.") - e = self.client.load_module(module) - if e: - await message.channel.send(f"An error occurred during the loading of the module {module}.") - await self.com_list(message, args, kwargs) - return - for arg in args: - e = self.client.unload_module(arg) - if e: - await message.channel.send(f"An error occurred during the unloading of the module {arg}.") - e = self.client.load_module(arg) - if e: - await message.channel.send(f"An error occurred during the loading of the 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(f"An error occurred during the loading of the module {module}.") - await self.com_list(message, args, kwargs) - return - for arg in args: - e = self.client.unload_module(arg) - if e: - await message.channel.send(f"An error occurred during the loading of the module {arg}: {e}.") - 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"]) - if len(activated): - activated_string = "\n+ " + "\n+ ".join(activated) - else: - activated_string = "" - if len(activated) != len(list_files): - deactivated_string = "\n- " + "\n- ".join(list_files.difference(activated)) - else: - deactivated_string = "" - embed = discord.Embed(title="[Modules] - Liste des modules", - description="```diff{activated}{deactivated}```".format( - activated=activated_string, - deactivated=deactivated_string) - ) - await message.channel.send(embed=embed) - - async def com_web_list(self, message, args, kwargs): - try: - modules = await self.api.list() - except ClientConnectorError: - await message.channel.send("Connection impossible au serveur.") - return - text = "" - for module, versions in modules.items(): - text += module + " - " + ", ".join(versions) - await message.channel.send(text) - - async def com_web_dl(self, message, args, kwargs): - try: - await self.api.download(args[1], args[2]) - except ClientConnectorError: - await message.channel.send("Connection impossible au serveur.") +import os + +import discord +from aiohttp import ClientConnectorError + +from modules.base import BaseClassPython +from modules.modules.api import Api + + +class MainClass(BaseClassPython): + name = "modules" + 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", + # "`{prefix}{command} web_source`": "List all source repositories", + # "`{prefix}{command} web_source remove `": "Remove url from repository list", + # "`{prefix}{command} web_source add `": "Add url to repository list", + } + } + + def __init__(self, client): + super().__init__(client) + os.makedirs("modules", exist_ok=True) + self.api = Api() + + @staticmethod + def get_all_modules(): + all_items = os.listdir("modules") + modules = [] + for item in all_items: + if item not in ["__init__.py", "base", "__pycache__"]: + 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 == 1: + await message.channel.send(f"Module {arg} not exists.") + if e == 2: + await message.channel.send(f"Module {arg} is incompatible.") + elif e: + await message.channel.send(f"An error occurred during the loading of the module {arg}: {e}.") + 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(f"An error occurred during the unloading of the module {module}.") + e = self.client.load_module(module) + if e: + await message.channel.send(f"An error occurred during the loading of the module {module}.") + await self.com_list(message, args, kwargs) + return + for arg in args: + e = self.client.unload_module(arg) + if e: + await message.channel.send(f"An error occurred during the unloading of the module {arg}.") + e = self.client.load_module(arg) + if e: + await message.channel.send(f"An error occurred during the loading of the 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(f"An error occurred during the loading of the module {module}.") + await self.com_list(message, args, kwargs) + return + for arg in args: + e = self.client.unload_module(arg) + if e: + await message.channel.send(f"An error occurred during the loading of the module {arg}: {e}.") + 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"]) + if len(activated): + activated_string = "\n+ " + "\n+ ".join(activated) + else: + activated_string = "" + if len(activated) != len(list_files): + deactivated_string = "\n- " + "\n- ".join(list_files.difference(activated)) + else: + deactivated_string = "" + embed = discord.Embed(title="[Modules] - Liste des modules", + description="```diff{activated}{deactivated}```".format( + activated=activated_string, + deactivated=deactivated_string) + ) + await message.channel.send(embed=embed) + + async def com_web_list(self, message, args, kwargs): + try: + modules = await self.api.list() + except ClientConnectorError: + await message.channel.send("Connection impossible au serveur.") + return + text = "" + for module, versions in modules.items(): + text += module + " - " + ", ".join(versions) + await message.channel.send(text) + + async def com_web_dl(self, message, args, kwargs): + try: + await self.api.download(args[1], args[2]) + except ClientConnectorError: + await message.channel.send("Connection impossible au serveur.") diff --git a/modules/modules/api.py b/src/modules/modules/api.py similarity index 100% rename from modules/modules/api.py rename to src/modules/modules/api.py diff --git a/modules/errors/version.json b/src/modules/modules/version.json similarity index 93% rename from modules/errors/version.json rename to src/modules/modules/version.json index 4cb3a98..85a57f9 100644 --- a/modules/errors/version.json +++ b/src/modules/modules/version.json @@ -1,14 +1,14 @@ -{ - "version": "0.1.0", - "type": "python", - "dependencies": { - "base": { - "min": "0.1.0", - "max": "0.1.0" - } - }, - "bot_version": { - "min": "0.1.0", - "max": "0.1.0" - } +{ + "version": "0.1.0", + "type": "python", + "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/newmember/__init__.py b/src/modules/newmember/__init__.py similarity index 100% rename from modules/newmember/__init__.py rename to src/modules/newmember/__init__.py diff --git a/src/modules/newmember/version.json b/src/modules/newmember/version.json new file mode 100644 index 0000000..53fa16b --- /dev/null +++ b/src/modules/newmember/version.json @@ -0,0 +1,11 @@ +{ + "version":"0.1.0", + "type": "python", + "dependencies": { + + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } +} \ No newline at end of file diff --git a/modules/panic/__init__.py b/src/modules/panic/__init__.py similarity index 100% rename from modules/panic/__init__.py rename to src/modules/panic/__init__.py diff --git a/src/modules/panic/version.json b/src/modules/panic/version.json new file mode 100644 index 0000000..53fa16b --- /dev/null +++ b/src/modules/panic/version.json @@ -0,0 +1,11 @@ +{ + "version":"0.1.0", + "type": "python", + "dependencies": { + + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } +} \ No newline at end of file diff --git a/modules/perdu/__init__.py b/src/modules/perdu/__init__.py similarity index 100% rename from modules/perdu/__init__.py rename to src/modules/perdu/__init__.py diff --git a/src/modules/perdu/version.json b/src/modules/perdu/version.json new file mode 100644 index 0000000..53fa16b --- /dev/null +++ b/src/modules/perdu/version.json @@ -0,0 +1,11 @@ +{ + "version":"0.1.0", + "type": "python", + "dependencies": { + + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } +} \ No newline at end of file diff --git a/modules/purge/__init__.py b/src/modules/purge/__init__.py similarity index 100% rename from modules/purge/__init__.py rename to src/modules/purge/__init__.py diff --git a/src/modules/purge/version.json b/src/modules/purge/version.json new file mode 100644 index 0000000..53fa16b --- /dev/null +++ b/src/modules/purge/version.json @@ -0,0 +1,11 @@ +{ + "version":"0.1.0", + "type": "python", + "dependencies": { + + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } +} \ No newline at end of file diff --git a/modules/readrules/__init__.py b/src/modules/readrules/__init__.py similarity index 100% rename from modules/readrules/__init__.py rename to src/modules/readrules/__init__.py diff --git a/src/modules/readrules/version.json b/src/modules/readrules/version.json new file mode 100644 index 0000000..53fa16b --- /dev/null +++ b/src/modules/readrules/version.json @@ -0,0 +1,11 @@ +{ + "version":"0.1.0", + "type": "python", + "dependencies": { + + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } +} \ No newline at end of file diff --git a/modules/restart/__init__.py b/src/modules/restart/__init__.py similarity index 100% rename from modules/restart/__init__.py rename to src/modules/restart/__init__.py diff --git a/src/modules/restart/version.json b/src/modules/restart/version.json new file mode 100644 index 0000000..53fa16b --- /dev/null +++ b/src/modules/restart/version.json @@ -0,0 +1,11 @@ +{ + "version":"0.1.0", + "type": "python", + "dependencies": { + + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } +} \ No newline at end of file diff --git a/modules/roles/__init__.py b/src/modules/roles/__init__.py similarity index 100% rename from modules/roles/__init__.py rename to src/modules/roles/__init__.py diff --git a/src/modules/roles/version.json b/src/modules/roles/version.json new file mode 100644 index 0000000..53fa16b --- /dev/null +++ b/src/modules/roles/version.json @@ -0,0 +1,11 @@ +{ + "version":"0.1.0", + "type": "python", + "dependencies": { + + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } +} \ No newline at end of file diff --git a/modules/rtfgd/__init__.py b/src/modules/rtfgd/__init__.py similarity index 100% rename from modules/rtfgd/__init__.py rename to src/modules/rtfgd/__init__.py diff --git a/src/modules/rtfgd/version.json b/src/modules/rtfgd/version.json new file mode 100644 index 0000000..53fa16b --- /dev/null +++ b/src/modules/rtfgd/version.json @@ -0,0 +1,11 @@ +{ + "version":"0.1.0", + "type": "python", + "dependencies": { + + }, + "bot_version": { + "min": "0.1.0", + "max": "0.1.0" + } +} \ No newline at end of file diff --git a/pytest.ini b/src/pytest.ini similarity index 100% rename from pytest.ini rename to src/pytest.ini diff --git a/storage/__init__.py b/src/storage/__init__.py similarity index 100% rename from storage/__init__.py rename to src/storage/__init__.py diff --git a/storage/jsonencoder.py b/src/storage/jsonencoder.py similarity index 100% rename from storage/jsonencoder.py rename to src/storage/jsonencoder.py diff --git a/storage/objects.py b/src/storage/objects.py similarity index 100% rename from storage/objects.py rename to src/storage/objects.py diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/emojis.py b/src/utils/emojis.py similarity index 100% rename from utils/emojis.py rename to src/utils/emojis.py