diff --git a/.gitignore b/.gitignore index de6ed85..dc3277f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,52 +1,3 @@ -# ---> Vim -[._]*.s[a-w][a-z] -[._]s[a-w][a-z] -*.un~ -Session.vim -.netrwhist -*~ - -# ---> Emacs -# -*- mode: gitignore; -*- -*~ -\#*\# -/.emacs.desktop -/.emacs.desktop.lock -*.elc -auto-save-list -tramp -.\#* - -# Org-mode -.org-id-locations -*_archive - -# flymake-mode -*_flymake.* - -# eshell files -/eshell/history -/eshell/lastdir - -# elpa packages -/elpa/ - -# reftex files -*.rel - -# AUCTeX auto folder -/auto/ - -# cask packages -.cask/ - -tmp/ +.idea __pycache__/* -*/__pycache__/* -.directory -storage/* -*.sha512 - -log - -.idea \ No newline at end of file +Pipfile.lock \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 7e6742b..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "fractale"] - path = fractale - url = https://moriya.zapto.org/LCI/fractale diff --git a/Makefile b/Makefile index 9159e0a..b044518 100755 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ storage/libs/python: storage/libs storage/libs/get-pip.py storage/libs/dependenc export LD_LIBRARY_PATH=$(mainDir)/storage/libs/libgit2/libgit2-0.27.0/installed/lib;\ export LIBGIT2=$(mainDir)/storage/libs/libgit2/libgit2-0.27.0/installed/;\ export PYTHONPATH=$(mainDir)/storage/libs/python:${PYTHONPATH};\ - python3.6 ~/.local/bin/pip3 install -U -t storage/libs/python -r dependencies + ~/.local/bin/pip3 install -U -t storage/libs/python -r dependencies touch $(mainDir)/storage/libs/python storage: cd $(mainDir);\ diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..4a1ce3d --- /dev/null +++ b/Pipfile @@ -0,0 +1,13 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +discord = {extras = ["voice", "doc"], ref = "rewrite", git = "https://github.com/Rapptz/discord.py"} +Pillow = "*" + +[dev-packages] + +[requires] +python_version = "3.7" diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f76607 --- /dev/null +++ b/README.md @@ -0,0 +1,131 @@ + +Bot discord Python pour le serveur officiel d'e-penser += +Ce bot est un supplément au [Bot JS](https://github.com/epenserdiscord/epenser-bot) + + +Installer le bot +- +```BASH +user : git clone https://github.com/epenserdiscord/epenser-bot-py.git +``` +Les dépendances seront installées au moment du lancement du bot. +excepté python (faut pas abuser non plus), +puisqu'on utilise la version rewrite de discord.py, +il faut python >= 3.5.3 + +Lancer le bot +- +```BASH +user : export DISCORD_TOKEN=token Remplacez par le token de votre bot. +user : cd path/to/epenser-bot-py/ +user : while true ; do git fetch --all; git reset --hard origin/master; make; done +``` +On peut aussi lancer le bot sans la boucle : +```BASH +user : export DISCORD_TOKEN=token Remplacez par le token de votre bot. +user : cd path/to/epenser-bot-py/ +user : make +``` +Daemoniser le bot +- +Le bot est actuellement organisé de manière à ce que la commande `/restart` quitte le programme et ce qu'un script à part vienne le relancer après avoir, par exemple, mis à jour les sources. Par conséquent, nous avons besoin d'un tel script. Celui qui est utilisé actuellement est séparé en plusieurs parties pour gérer les deux sections (JS et Python) avec : + +`/home/epenser/run` (marqué en executable) +```BASH +#!/bin/bash +export DISCORD_TOKEN=token +export GITHUB_TOKEN=token +sleep 20 #C'est pour le réseau :3 +bash /home/epenser/pybot.sh & +bash /home/epenser/jsbot.sh +``` +On ne s'intéresse ici pas au `jsbot.sh` mais uniquement `pybot.sh` : + +`/home/epenser/pybot.sh` +```BASH +#!/bin/bash +set -o pipefail +cd /home/epenser/epenser-bot/epenser-bot-py/ +while [ true ] ;do +date >> log +echo "---- git pull ----" >> log +echo >> log +git fetch --all 2>&1 | tee -a log +git reset --hard origin/master 2>&1 | tee -a log +echo >> log +./main.py 2>&1 | tee -a log +done +``` +Le serveur utilisé pour hoster le bot est un raspberry sous raspbian. Il est donc sous systemd. Je ne me suis pas intéressé à Open-RC même si j'aurais pu. + +`/etc/init.d/epenser` +```BASH +#!/bin/sh -e +### BEGIN INIT INFO +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +### END INIT INFO +DAEMON="/home/epenser/run" +daemon_OPT="" +DAEMONUSER="epenser" +daemon_NAME="run" + +PATH="/sbin:/bin:/usr/sbin:/usr/bin" + +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +d_start () { +log_daemon_msg "Starting system $daemon_NAME Daemon" +start-stop-daemon --background --name $daemon_NAME --start --quiet --chuid $DAEMONUSER --exec $DAEMON -- $daemon_OPT +log_end_msg $? +} + +d_stop () { +log_daemon_msg "Stopping system $daemon_NAME Daemon" +start-stop-daemon --name $daemon_NAME --stop --retry 5 --quiet --name $daemon_NAME +log_end_msg $? +} + +case "$1" in + +start|stop) +d_${1} +;; + +restart|reload|force-reload) +d_stop +d_start +;; + +force-stop) +d_stop +killall -q $daemon_NAME || true +sleep 2 +killall -q -9 $daemon_NAME || true +;; + +status) +status_of_proc "$daemon_NAME" "$DAEMON" "system-wide $daemon_NAME" && exit 0 || exit $? +;; +*) +echo "Usage: /etc/init.d/$daemon_NAME {start|stop|force-stop|restart|reload|force-reload|status}" +exit 1 +;; +esac +exit 0 +``` +Pensez à remplacer les parties spécifiques. + +(Il y a probablement des erreurs dans cette partie, j'ai jamais rien compris à systemD :( ) + +```BASH +root : systemctl daemon-reload +root : systemctl enable epenser.service +root : systemctl add-wants $(systemctl get-default) epenser.service +``` +En théorie, à partir de là, le service est installé. Si on redémarre le système, le service se lancera au démarrage. +Seul problème : Impossible de l'arrêter. +TODO : Modifier le script pour qu'on puisse l'arrêter. diff --git a/assets/DejaVuSerif-Bold.ttf b/assets/DejaVuSerif-Bold.ttf new file mode 100644 index 0000000..3bb755f Binary files /dev/null and b/assets/DejaVuSerif-Bold.ttf differ diff --git a/assets/Hack-Bold.ttf b/assets/Hack-Bold.ttf new file mode 100644 index 0000000..7ff4975 Binary files /dev/null and b/assets/Hack-Bold.ttf differ diff --git a/assets/ba-dum-tss.mp3 b/assets/ba-dum-tss.mp3 new file mode 100644 index 0000000..8dad898 Binary files /dev/null and b/assets/ba-dum-tss.mp3 differ diff --git a/assets/for-the-damaged-coda.mp3 b/assets/for-the-damaged-coda.mp3 new file mode 100644 index 0000000..1117185 Binary files /dev/null and b/assets/for-the-damaged-coda.mp3 differ diff --git a/assets/pillar-men-theme.mp3 b/assets/pillar-men-theme.mp3 new file mode 100644 index 0000000..b6c2059 Binary files /dev/null and b/assets/pillar-men-theme.mp3 differ diff --git a/assets/roundabout-long.mp3 b/assets/roundabout-long.mp3 new file mode 100644 index 0000000..e359137 Binary files /dev/null and b/assets/roundabout-long.mp3 differ diff --git a/assets/roundabout-short.mp3 b/assets/roundabout-short.mp3 new file mode 100644 index 0000000..13bca00 Binary files /dev/null and b/assets/roundabout-short.mp3 differ diff --git a/assets/see-you-again.mp3 b/assets/see-you-again.mp3 new file mode 100644 index 0000000..7599674 Binary files /dev/null and b/assets/see-you-again.mp3 differ diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..e59dd7f --- /dev/null +++ b/config/config.json @@ -0,0 +1 @@ +{"modules": ["modules"], "prefix": "%"} \ No newline at end of file diff --git a/config/log_config.json b/config/log_config.json new file mode 100644 index 0000000..cd5bbad --- /dev/null +++ b/config/log_config.json @@ -0,0 +1,54 @@ +{ + "version": 1, + "disable_existing_loggers": false, + "formatters": { + "simple": { + "format": "%(asctime)s :: %(name)s :: %(levelname)s :: %(message)s" + } + }, + + "handlers": { + "console": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "formatter": "simple", + "stream": "ext://sys.stdout" + }, + + "info_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "INFO", + "formatter": "simple", + "filename": "info.log", + "maxBytes": 1048576, + "backupCount": 20, + "encoding": "utf8" + }, + + "error_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "ERROR", + "formatter": "simple", + "filename": "errors.log", + "maxBytes": 1048576, + "backupCount": 20, + "encoding": "utf8" + } + }, + + "loggers": { + "LBI": { + "level": "INFO", + "handlers": ["console", "info_file_handler", "error_file_handler"] + }, + "discord": { + "level":"WARNING", + "handlers":["console", "info_file_handler", "error_file_handler"] + } + }, + + "root": { + "level": "INFO", + "handlers": [] + } +} \ No newline at end of file diff --git a/dependencies b/dependencies index 501d437..714fef2 100644 --- a/dependencies +++ b/dependencies @@ -1,2 +1,2 @@ -https://github.com/Rapptz/discord.py/archive/rewrite.zip#egg=discord.py +https://github.com/Rapptz/discord.py/archive/rewrite.zip#egg=discord.py[voice] Pillow==5.2.0 diff --git a/fractale b/fractale deleted file mode 160000 index 0138149..0000000 --- a/fractale +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 013814928c567deb2272581f7a27c2b1e15eec2f diff --git a/main.py b/main.py index 6f996f5..92e8037 100755 --- a/main.py +++ b/main.py @@ -1,398 +1,460 @@ #!/usr/bin/python3 + import importlib +import json +import logging +import logging.config import os import traceback import discord -client = discord.Client() -prefix = ';' -modules = {} # format : {'modulename':[module, initializedclass]} -owners = [281166473102098433, 318866596502306816, 436105272310759426] +from modules.base import BaseClass -async def auth(user, module_name): - if user.id in owners: - return True - try: - modules[module_name][1].authlist - except: - return True - for guild in client.guilds: - if guild.get_member(user.id): - for roleid in modules[module_name][1].authlist: - if roleid in [r.id for r in guild.get_member(user.id).roles]: - return True +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) -@client.event -async def on_ready(): - print("Bienvenue, {0.user}, l'heure est venue de faire des fractales.".format(client)) - panic = False - error = None +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 - async def panic_load(): - print("--PANIC LOAD--") - panic = True - modules = {} - for filename in os.listdir('modules'): - if filename.endswith('.py'): - try: - modules.update({filename[:-3:]: [importlib.import_module('modules.' + filename[:-3:])]}) - print("Module {0} chargé.".format(filename[:-3:])) - except: - print("[ERROR] Le module {0} n'a pas pu être chargé.".format(filename)) - # initialisation - for module_name in list(modules.keys()): - try: - modules[module_name].append(modules[module_name][0].MainClass(client, modules, owners, prefix)) - print("Module {0} initialisé.".format(module_name)) - except: - print("[ERROR] Le module {0} n'a pas pu être initialisé.".format(module_name)) - modules.pop(module_name, None) + return wrapper - if 'modules.py' in os.listdir('modules'): + +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 + + +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): try: - modules.update({'modules': [importlib.import_module('modules.' + 'modules')]}) - print("Module {0} chargé.".format('modules')) + info("Start loading module {module}...".format(module=module)) + imported = importlib.import_module('modules.' + module) + if issubclass(imported.MainClass, BaseClass): + 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() + else: + error("Module {module} isn't an instance of BaseClass.".format(module=module)) + except AttributeError as e: + error("Module {module} doesn't have MainClass.".format(module=module)) + return e + + @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_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 get_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: - modules['modules'].append(modules['modules'][0].MainClass(client, modules, owners, prefix)) - print("Module {0} initialisé.".format('modules')) - try: - await modules['modules'][1].on_ready() - except Exception as e: - error = e - except Exception as e: - print("[ERROR] Le module {0} n'a pas pu être initialisé.".format('modules')) - await panic_load() - error = e - except Exception as e: - print("[ERROR] Le module {0} n'a pas pu être chargé.".format('modules.py')) - await panic_load() - error = e - else: - await panic_load() + return await channel.get_message(id_, *args, **kwargs) + except discord.NotFound: + continue + if msg is None: + raise discord.NotFound(None, "Message not found") - if panic: - for moduleName in list(modules.keys()): - if 'on_ready' in modules[moduleName][1].events: - await modules[moduleName][1].on_ready() - else: - for moduleName in list(modules.keys()): - if (not moduleName == 'modules') and 'on_ready' in modules[moduleName][1].events: - await modules[moduleName][1].on_ready() - if error: - raise error + 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.get_message(id_) + return await message.edit(**kwargs) -@client.event -async def on_error(event, *args, **kwargs): - print(traceback.format_exc()) - for moduleName in list(modules.keys()): - if 'on_error' in modules[moduleName][1].events: - await modules[moduleName][1].on_error(event, *args, **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.get_message(id_message) + return await message.remove_reaction(*args, **kwargs) -@client.event -async def on_socket_raw_receive(msg): - for moduleName in list(modules.keys()): - if 'on_socket_raw_receive' in modules[moduleName][1].events: - await modules[moduleName][1].on_socket_raw_receive(msg) + 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.event -async def on_socket_raw_send(payload): - for moduleName in list(modules.keys()): - if 'on_socket_raw_send' in modules[moduleName][1].events: - await modules[moduleName][1].on_socket_raw_send(payload) - -@client.event -async def on_typing(channel, user, when): - for moduleName in list(modules.keys()): - if 'on_typing' in modules[moduleName][1].events: - await modules[moduleName][1].on_typing(channel, user, when) - - -@client.event -async def on_message(message): - for moduleName in list(modules.keys()): - if 'on_message' in modules[moduleName][1].events and message.content.startswith(modules[moduleName][1].command): - if await auth(message.author, moduleName): - await modules[moduleName][1].on_message(message) - - -@client.event -async def on_message_delete(message): - for moduleName in list(modules.keys()): - if 'on_message_delete' in modules[moduleName][1].events: - await modules[moduleName][1].on_message_delete(message) - - -@client.event -async def on_raw_message_delete(payload): - for moduleName in list(modules.keys()): - if 'on_raw_message_delete' in modules[moduleName][1].events: - await modules[moduleName][1].on_raw_message_delete(payload) - - -@client.event -async def on_raw_bulk_message_delete(payload): - for moduleName in list(modules.keys()): - if 'on_raw_bulk_message_delete' in modules[moduleName][1].events: - await modules[moduleName][1].on_raw_bulk_message_delete(payload) - - -@client.event -async def on_message_edit(before, after): - for moduleName in list(modules.keys()): - if 'on_message_edit' in modules[moduleName][1].events: - await modules[moduleName][1].on_message_edit(before, after) - - -@client.event -async def on_raw_message_edit(payload): - for moduleName in list(modules.keys()): - if 'on_raw_message_edit' in modules[moduleName][1].events: - await modules[moduleName][1].on_raw_message_edit(payload) - - -@client.event -async def on_reaction_add(reaction, user): - for moduleName in list(modules.keys()): - if 'on_reaction_add' in modules[moduleName][1].events: - await modules[moduleName][1].on_reaction_add(reaction, user) - - -@client.event -async def on_raw_reaction_add(payload): - for moduleName in list(modules.keys()): - if 'on_raw_reaction_add' in modules[moduleName][1].events: - await modules[moduleName][1].on_raw_reaction_add(payload) - - -@client.event -async def on_reaction_remove(reaction, user): - for moduleName in list(modules.keys()): - if 'on_reaction_remove' in modules[moduleName][1].events: - await modules[moduleName][1].on_reaction_remove(reaction, user) - - -@client.event -async def on_raw_reaction_remove(payload): - for moduleName in list(modules.keys()): - if 'on_raw_reaction_remove' in modules[moduleName][1].events: - await modules[moduleName][1].on_raw_reaction_remove(payload) - - -@client.event -async def on_reaction_clear(message, reactions): - for moduleName in list(modules.keys()): - if 'on_reaction_clear' in modules[moduleName][1].events: - await modules[moduleName][1].on_reaction_clear(message, reactions) - - -@client.event -async def on_raw_reaction_clear(payload): - for moduleName in list(modules.keys()): - if 'on_raw_reaction_clear' in modules[moduleName][1].events: - await modules[moduleName][1].on_raw_reaction_clear(payload) - - -@client.event -async def on_private_channel_delete(channel): - for moduleName in list(modules.keys()): - if 'on_private_channel_delete' in modules[moduleName][1].events: - await modules[moduleName][1].on_private_channel_delete(channel) - - -@client.event -async def on_private_channel_create(channel): - for moduleName in list(modules.keys()): - if 'on_private_channel_create' in modules[moduleName][1].events: - await modules[moduleName][1].on_private_channel_create(channel) - - -@client.event -async def on_private_channel_update(before, after): - for moduleName in list(modules.keys()): - if 'on_private_channel_update' in modules[moduleName][1].events: - await modules[moduleName][1].on_private_channel_update(before, after) - - -@client.event -async def on_private_channel_pins_update(channel, last_pin): - for moduleName in list(modules.keys()): - if 'on_private_channel_pins_update' in modules[moduleName][1].events: - await modules[moduleName][1].on_private_channel_pins_update(channel, last_pin) - - -@client.event -async def on_guild_channel_delete(channel): - for moduleName in list(modules.keys()): - if 'on_guild_channel_delete' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_channel_delete(channel) - - -@client.event -async def on_guild_channel_create(channel): - for moduleName in list(modules.keys()): - if 'on_guild_channel_create' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_channel_create(channel) - - -@client.event -async def on_guild_channel_update(before, after): - for moduleName in list(modules.keys()): - if 'on_guild_channel_update' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_channel_update(before, after) - - -@client.event -async def on_guild_channel_pins_update(channel, last_pin): - for moduleName in list(modules.keys()): - if 'on_guild_channel_pins_update' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_channel_pins_update(channel, last_pin) - - -@client.event -async def on_member_join(member): - for moduleName in list(modules.keys()): - if 'on_member_join' in modules[moduleName][1].events: - await modules[moduleName][1].on_member_join(member) - - -@client.event -async def on_member_remove(member): - for moduleName in list(modules.keys()): - if 'on_member_remove' in modules[moduleName][1].events: - await modules[moduleName][1].on_member_remove(member) - - -@client.event -async def on_member_update(before, after): - for moduleName in list(modules.keys()): - if 'on_member_update' in modules[moduleName][1].events: - await modules[moduleName][1].on_member_update(before, after) - - -@client.event -async def on_guild_join(guild): - for moduleName in list(modules.keys()): - if 'on_guild_join' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_join(guild) - - -@client.event -async def on_guild_remove(guild): - for moduleName in list(modules.keys()): - if 'on_guild_remove' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_remove(guild) - - -@client.event -async def on_guild_update(before, after): - for moduleName in list(modules.keys()): - if 'on_guild_update' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_update(before, after) - - -@client.event -async def on_guild_role_create(role): - for moduleName in list(modules.keys()): - if 'on_guild_role_create' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_role_create(role) - - -@client.event -async def on_guild_role_delete(role): - for moduleName in list(modules.keys()): - if 'on_guild_role_delete' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_role_delete(role) - - -@client.event -async def on_guild_role_update(before, after): - for moduleName in list(modules.keys()): - if 'on_guild_role_update' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_role_update(before, after) - - -@client.event -async def on_guild_emojis_update(guild, before, after): - for moduleName in list(modules.keys()): - if 'on_guild_emojis_update' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_emojis_update(guild, before, after) - - -@client.event -async def on_guild_available(guild): - for moduleName in list(modules.keys()): - if 'on_guild_available' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_available(guild) - - -@client.event -async def on_guild_unavailable(guild): - for moduleName in list(modules.keys()): - if 'on_guild_unavailable' in modules[moduleName][1].events: - await modules[moduleName][1].on_guild_unavailable(guild) - - -@client.event -async def on_voice_state_update(member, before, after): - for moduleName in list(modules.keys()): - if 'on_voice_state_update' in modules[moduleName][1].events: - await modules[moduleName][1].on_voice_state_update(member, before, after) - - -@client.event -async def on_member_ban(guild, user): - for moduleName in list(modules.keys()): - if 'on_member_ban' in modules[moduleName][1].events: - await modules[moduleName][1].on_member_ban(guild, user) - - -@client.event -async def on_member_unban(guild, user): - for moduleName in list(modules.keys()): - if 'on_member_unban' in modules[moduleName][1].events: - await modules[moduleName][1].on_member_unban(guild, user) - - -@client.event -async def on_group_join(channel, user): - for moduleName in list(modules.keys()): - if 'on_group_join' in modules[moduleName][1].events: - await modules[moduleName][1].on_group_join(channel, user) - - -@client.event -async def on_group_remove(channel, user): - for moduleName in list(modules.keys()): - if 'on_group_remove' in modules[moduleName][1].events: - await modules[moduleName][1].on_group_remove(channel, user) - - -@client.event -async def on_relationship_add(relationship): - for moduleName in list(modules.keys()): - if 'on_relationship_add' in modules[moduleName][1].events: - await modules[moduleName][1].on_relationship_add(relationship) - - -@client.event -async def on_relationship_remove(relationship): - for moduleName in list(modules.keys()): - if 'on_relationship_remove' in modules[moduleName][1].events: - await modules[moduleName][1].on_relationship_remove(relationship) - - -@client.event -async def on_relationship_update(before, after): - for moduleName in list(modules.keys()): - if 'on_relationship_update' in modules[moduleName][1].events: - await modules[moduleName][1].on_relationship_update(before, after) - - -client.run(os.environ['DISCORD_TOKEN']) +client = LBI() +client.run("NTUwMDkxOTAyMDY2ODg0NjA4.XKfETw.v150BqB-fMo2wCRuSsTLuKXAOyE", 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.py b/modules/base.py new file mode 100644 index 0000000..abc14eb --- /dev/null +++ b/modules/base.py @@ -0,0 +1,412 @@ +"""Base class for module, never use directly !!!""" +import os +import pickle +import zipfile + +import discord +from packaging import version + + +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 = [] + version = version.parse("0.0.1") + min_base_version = version.parse("0.0.1") + max_base_version = "0.0.1" + + 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/dummy.py b/modules/dummy.py deleted file mode 100644 index 76268a8..0000000 --- a/modules/dummy.py +++ /dev/null @@ -1,19 +0,0 @@ -class MainClass: - def __init__(self, client, modules, owners, prefix): - self.client = client - self.modules = modules - self.owners = owners - self.prefix = prefix - self.events = ['on_message'] # events list - self.command = "%sdummy" % self.prefix # command prefix (can be empty to catch every single messages) - - self.name = "Dummy" - self.description = "Module d'exemple" - self.interactive = False - self.color = 0x000000 - self.help = """\ - Aucune fonction. -""" - - async def on_message(self, message): - print(message.content) diff --git a/modules/errors.py b/modules/errors.py index e890262..a91327b 100644 --- a/modules/errors.py +++ b/modules/errors.py @@ -1,59 +1,52 @@ import asyncio import collections -import os.path -import pickle import random import traceback -from subprocess import call import discord -moduleFiles = "errors" +from modules.base import BaseClass -class MainClass: - def save_object(self, object_instance, object_name): - with open("storage/%s/" % moduleFiles + object_name + "tmp", "wb") as pickleFile: - pickler = pickle.Pickler(pickleFile) - pickler.dump(object_instance) - call(['mv', "storage/%s/" % moduleFiles + object_name + "tmp", "storage/%s/" % moduleFiles + object_name]) +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 load_object(self, objectname): - if self.save_exists(objectname): - with open("storage/%s/" % moduleFiles + objectname, "rb") as pickleFile: - unpickler = pickle.Unpickler(pickleFile) - return unpickler.load() - - def save_exists(self, objectname): - return os.path.isfile("storage/%s/" % moduleFiles + objectname) - - def __init__(self, client, modules, owners, prefix): - if not os.path.isdir("storage/%s" % moduleFiles): - call(['mkdir', 'storage/%s' % moduleFiles]) + def __init__(self, client): + super().__init__(client) self.errorsDeque = None - self.developpement_chan_id = [549662392120901633] + self.development_chan_id = [456142390726623243, 473637619310264330, 474267318332030987] self.memes = [ - "https://cdn.discordapp.com/avatars/436105272310759426/6e6850c03fba976f45295a76410a6699.png?size=64", - "https://cdn.discordapp.com/avatars/281166473102098433/6e92bff42c9f409334e1580c9f666228.png?size=64", - "https://cdn.discordapp.com/avatars/318866596502306816/8e1792132f5b9ceaca6f7aea1fd489f0.png?size=64" + "https://cdn.discordapp.com/attachments/430408983283761152/430433931272126465/Bruce_3.png", + "https://cdn.discordapp.com/attachments/430408983283761152/430431622521946123/LUNE.jpg", + "https://cdn.discordapp.com/attachments/326742676672086018/431570139016724480/27th3c.png", + "https://cdn.discordapp.com/attachments/326742676672086018/431570538868244492/27th8d.png", + "https://cdn.discordapp.com/attachments/326742676672086018/431570620942123009/lemdpc3.png", + "https://cdn.discordapp.com/attachments/326742676672086018/431570838026846208/telecharge_15.jpg", + "https://cdn.discordapp.com/attachments/326742676672086018/431571174078808070/telecharge_16.jpg", + "https://cdn.discordapp.com/attachments/326742676672086018/431571655115145217/unknown.png", + "https://cdn.discordapp.com/attachments/326742676672086018/431574206518525963/Bruce_troll_QHwYz39nj7i.png", + "https://cdn.discordapp.com/attachments/326742676672086018/431572693910028289/telecharge_19.jpg" + "https://cdn.discordapp.com/attachments/434475794631360512/447168326582665241/2akl04.jpg", + "https://cdn.discordapp.com/attachments/434475794631360512/447168125067067394/20180519_004620.png", + "https://cdn.discordapp.com/attachments/434475794631360512/446441679868788736/Sans_titre_0.png", + "https://cdn.discordapp.com/attachments/434475794631360512/446441465611026443/unknown.png", + "https://cdn.discordapp.com/attachments/297868535076749323/445789702373638164/image.png", + "https://cdn.discordapp.com/attachments/297868535076749323/297875363160129540/unknown.png", + "https://cdn.discordapp.com/attachments/326742316456869888/447887603664945163/unknown.png", + "https://cdn.discordapp.com/attachments/434475794631360512/460876662414901249/TeslaPLS.png" ] self.icon = "https://cdn.discordapp.com/attachments/340620490009739265/431569015664803840/photo.png" - self.client = client - self.modules = modules - self.owners = owners - self.prefix = prefix - self.events = ['on_error', 'on_message', 'on_ready'] # events list - self.command = "%slicorne" % self.prefix # command prefix (can be empty to catch every single messages) - - self.name = "Error Handling" - self.description = "Module de gestions des erreurs" - self.interactive = True - self.super_users_list = [431043517217898496] - self.color = 0xdb1348 - self.help = """\ - licorne - => Crée une erreur. -""" async def on_ready(self): if self.save_exists('errorsDeque'): @@ -70,8 +63,8 @@ class MainClass: raise self.save_object(self.errorsDeque, 'errorsDeque') - async def on_message(self, message): - 5 / 0 + async def command(self, message, args, kwargs): + raise BaseException("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()), @@ -79,18 +72,18 @@ class MainClass: message_list = None try: message = await args[0].channel.send( - embed=embed.set_footer(text="Ce message s'autodétruira dans une minute.", icon_url=self.icon)) + 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 s'autodétruira dans une minute.", icon_url=self.icon)) + 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.developpement_chan_id: + 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)) diff --git a/modules/fractale.py b/modules/fractale.py deleted file mode 100644 index 60c4863..0000000 --- a/modules/fractale.py +++ /dev/null @@ -1,218 +0,0 @@ -# dummy module -import discord -import random - -import fractale.source.main -from PIL import Image -class MainClass(): - def __init__(self, client, modules, owners, prefix): - self.client = client - self.modules = modules - self.owners = owners - self.prefix = prefix - self.events = ['on_message'] # events list - self.command = "%sfractale" % self.prefix # command prefix (can be empty to catch every single messages) - - self.name = "Fractale" - self.description = "Module de génération de fractales" - self.interactive = True - self.color = 0x78ffc3 - self.fractals = { - "von_koch_curve_flake": {"Type": "Figures", "Max": ((5000, 5000), 5000, 10, 16777215, 16777215, 5000), "Min": ((0, 0), 0, 1, 0, 0, 0), - "Default": "(2500 2500) 2000 5 #81ff65 #191919 2", - "Indication": "(départ) rayon longueur iterations couleur_trait couleur_fond stroke", - "ParseData": "pfixxi"}, - "von_koch_curve": {"Type": "Figures", "Max": ((5000, 5000), (5000, 5000), 10, 16777215, 16777215, 5000), "Min": ((0, 0), (0, 0), 1, 0, 0, 0), - "Default": "(0 2500) (5000 2500) 5 #81ff65 #191919 2", - "Indication": "(départ) (arrivée) iterations couleur_trait couleur_fond stroke", "ParseData": "ppixxi"}, - "blanc_manger": {"Type": "Figures", "Max": ((5000, 5000), (5000, 5000), 10, 16777215, 16777215, 5000), "Min": ((0, 0), (0, 0), 1, 0, 0, 0), - "Default": "(1000 1000) (4000 4000) 7 #81ff65 #191919 2", - "Indication": "(départ) (arrivée) iterations couleur_trait couleur_fond stroke", "ParseData": "ppixxi"}, - "dragon": {"Type": "Lsystem", "Max": ((5000, 5000), 2500, 19, 16777215, 16777215, 5000), "Min": ((0, 0), 1, 1, 0, 0, 0), - "Default": "(2500 2500) 4 18 #81ff65 #191919 2", "Indication": "(origine) longueur iterations couleur_trait couleur_fond stroke", - "ParseData": "pfixxi"}, - "sierpinski_triangle": {"Type": "Lsystem", "Max": ((5000, 5000), 2500, 11, 16777215, 16777215, 5000), "Min": ((0, 0), 0, 1, 0, 0, 0), - "Default": "(0 0) 10 9 #81ff65 #191919 2", "Indication": "(origine) longueur iterations couleur_trait couleur_fond stroke", - "ParseData": "pfixxi"}, - "fractal_plant": {"Type": "Lsystem", "Max": ((5000, 5000), 2500, 8, 16777215, 16777215, 5000), "Min": ((0, 0), 0, 1, 0, 0, 0), - "Default": "(0 2500) 6.5 8 #81ff65 #191919 2", "Indication": "(origine) longueur iterations couleur_trait couleur_fond stroke", - "ParseData": "pfixxi"}, - "koch_curve_right_angle": {"Type": "Lsystem", "Max": ((5000, 5000), 2500, 9, 16777215, 16777215, 5000), "Min": ((0, 0), 0, 1, 0, 0, 0), - "Default": "(0 5000) 2.25 7 #81ff65 #191919 2", - "Indication": "(origine) longueur iterations couleur_trait couleur_fond stroke", - "ParseData": "pfixxi"}, - "fractal_binary_tree": {"Type": "Lsystem", "Max": ((5000, 5000), 2500, 15, 16777215, 16777215, 5000), "Min": ((0, 0), 0, 1, 0, 0, 0), - "Default": "(0 2500) 1.2 12 #81ff65 #191919 2", "Indication": "(origine) longueur iterations couleur_trait couleur_fond stroke", - "ParseData": "pfixxi"} - } - self.help = """\ - fractale info - => Affiche les informations relatives à la fractale spécifiée. (paramètres attendus, paramètres par défaut, type des arguments) - - fractale - => Génère une image fractale à partir des arguments fournis. Pour mettre la valeur par défaut pour un des arguments, le remplacer par le caractère * - - fractale - => Génère une image fractale avec les paramètres par défaut. - - -> Valeurs possible pour -```..: Toutes les fractales: -%s```""" % '\n'.join(['......: %s' % t for t in self.fractals.keys()]) - - async def on_message(self, message): - async with message.channel.typing(): - args = message.content.split(" ") - if len(args) == 1: - await self.modules['help'][1].send_help(message.channel, self) - elif len(args) == 2: - if args[1] in self.fractals.keys(): - await self.send_fractal(message, args[1]+' '+' '.join(['*']*len(self.fractals[args[1]]['ParseData']))) - else: - await self.modules['help'][1].send_help(message.channel, self) - elif len(args) == 3: - if args[1] == "info" and args[2] in self.fractals.keys(): - description = """\ - La fractale {nom} attend les arguments suivant : - `{arguments}` - avec le type suivant: - `{type}` - Les nombres décimaux sont des nombres à virgules là où les nombres entiers n'en ont pas. **(mettre un point à la place de la virgule)** - - Attention, les coordonnées des points doivent être entre parentheses et sont considérées comme un unique argument. - Gardez aussi en tête que **l'image a pour résolution 5000*5000.** - - Les arguments valent par défaut : - `{defaut}`""".format(nom=args[2], arguments=self.fractals[args[2]]['Indication'], type=' '.join(["point decimal entier hexadecimal".split(" ")["pfix".index(ch)] for ch in self.fractals[args[2]]['ParseData']]), defaut=self.fractals[args[2]]['Default']) - await message.channel.send( - embed=discord.Embed(title="[%s] - Infos : *%s*" % (self.name, args[2]), description=description, color=self.color)) - else: - await self.modules['help'][1].send_help(message.channel, self) - elif args[1] in self.fractals.keys(): - await self.send_fractal(message, ' '.join(args[1:])) - else: - await self.modules['help'][1].send_help(message.channel, self) - - async def send_fractal(self, message, command): - parsed_data=self.parse(command) - if parsed_data["Success"]: - res=parsed_data["Result"] - tmpstr="/tmp/%s.png"%random.randint(1,10000000) - im=Image.new('RGB', (5000,5000), tuple([int(i, 16) for i in map(''.join, zip(*[iter(hex(res[-2])[2:].ljust(6,'0'))]*2))])) - fig = eval("fractale.source.main."+self.fractals[command.split(' ')[0]]["Type"]+"(im=im)") - if self.fractals[command.split(' ')[0]]["Type"]=="Lsystem": - fig.state.x,fig.state.y=res[0] - #fig.state.color,fig.state.width=tuple([int(i, 16) for i in map(''.join, zip(*[iter(hex(res[-3])[2:].ljust(6,'0'))]*2))]),res[-1] - eval("fig."+command.split(' ')[0])(*res[1:-3],color=tuple([int(i, 16) for i in map(''.join, zip(*[iter(hex(res[-3])[2:].ljust(6,'0'))]*2))]), width=res[-1]) - else: - eval("fig."+command.split(' ')[0])(*res[:-3],color=tuple([int(i, 16) for i in map(''.join, zip(*[iter(hex(res[-3])[2:].ljust(6,'0'))]*2))]), width=res[-1]) - im.save(tmpstr) - await message.channel.send(file=discord.File(tmpstr)) - else: - await message.channel.send(parsed_data["Message"]) - - def parse(self, inp): - retDic = {"Success": False, "Message": "", "Result": ()} - # Parsing the fractal name and storing the corresponding dic into a variable - try: - fractal = self.fractals[inp.split(' ')[0]] - except KeyError: - retDic.update({"Success": False, "Message": "La fractale %s n'existe pas." % inp.split(' ')[0]}) - return (retDic) - arg = ' '.join(inp.split(' ')[1:]) # Stuff after the fractal name - # checking for incoherent parentheses usage - parentheses_count = 0 - for i, char in enumerate(arg): - if char == '(': - parentheses_count += 1 - elif char == ')': - parentheses_count -= 1 - if not (-1 < parentheses_count < 2): - retDic.update({"Success": False, - "Message": "Usage invalide de parentheses au charactère numéro %s (à partir d'après le nom de la fractale)." % i}) - return (retDic) - - # Here, we have a coherent parentheses usage - if ',' in arg: - retDic.update({"Success": False, - "Message": "Les virgules n'ont pas leur place dans les paramètres de génération. Il ne doit y avoir que des espaces uniques."}) - return (retDic) - - # parsing the fractal - args = arg.replace(')', '').replace('(', '').split(' ') - parsed_args = [] - i = 0 - for parse in fractal['ParseData']: - if parse == 'p': - if args[i] != '*': - try: - int(args[i]) - int(args[i + 1]) - except: - retDic.update({"Success": False, - "Message": "Les valeurs ne sont pas du bon type. (nombre entiers attendus pour les coordonnées d'un point)"}) - return (retDic) - parsed_args.append((int(args[i]), int(args[i + 1]))) - i += 2 - else: - parsed_args.append( - self.parse(inp.split(' ')[0] + ' ' + fractal['Default'])['Result'][len(parsed_args)]) - i += 1 - - elif parse == 'f': - if args[i] != '*': - try: - float(args[i]) - except: - retDic.update({"Success": False, - "Message": "Les valeurs ne sont pas du bon type. (Nombre à virgule flottante attendu (mettre un point pour la virgule))"}) - return (retDic) - parsed_args.append(float(args[i])) - i += 1 - else: - parsed_args.append( - self.parse(inp.split(' ')[0] + ' ' + fractal['Default'])['Result'][len(parsed_args)]) - i += 1 - - elif parse == 'i': - if args[i] != '*': - try: - int(args[i]) - except: - retDic.update({"Success": False, - "Message": "Les valeurs ne sont pas du bon type. (Nombre entier attendu)"}) - return (retDic) - parsed_args.append(int(args[i])) - i += 1 - else: - parsed_args.append( - self.parse(inp.split(' ')[0] + ' ' + fractal['Default'])['Result'][len(parsed_args)]) - i += 1 - - elif parse == 'x': - if args[i] != '*': - try: - if '#' in args[i]: - int(args[i].replace('#', '0x'), 16) - else: - raise - except: - retDic.update({"Success": False, - "Message": "Les valeurs ne sont pas du bon type. (Valeur hexadécimale attendue)"}) - return (retDic) - parsed_args.append(int(args[i].replace('#', '0x'), 16)) - i += 1 - else: - parsed_args.append( - self.parse(inp.split(' ')[0] + ' ' + fractal['Default'])['Result'][len(parsed_args)]) - i += 1 - for i,element in enumerate(parsed_args): - if element>fractal['Max'][i]: - retDic.update({"Success": False, - "Message": "Les valeurs sont trop grandes (%s > %s)"%(str(element),str(fractal['Max'][i]))}) - return retDic - elif elementgit update - => Execute les commandes suivantes dans le dossier du bot: - ```BASH - git fetch --all - git reset --hard origin/``` -""" + super_users = [431043517217898496] - async def on_message(self, message): - args = message.content.split(' ') - if len(args) == 2 and args[1] == 'update': - with os.popen('git fetch --all') as std_out: - await message.channel.send(std_out.read()) - with os.popen('git symbolic-ref HEAD 2>/dev/null') as std_out: - branch = std_out.read().replace('refs/heads/', '') - with os.popen('git reset --hard origin/%s' % branch) as std_out: - await message.channel.send(std_out.read()) - await message.channel.send(message.author.mention + ", Le dépôt a été mis à jour (fetch + reset --hard).") - else: - await self.modules['help'][1].send_help(message.channel, self) + color = 0x000000 + + help_active = True + help = { + "description": "Module gérant les redémarages du bot", + "commands": { + "`{prefix}{command} update`": "Execute les commandes suivantes dans le dossier du bot:" + "```BASH\n" + "git fetch --all " + "git reset --hard origin/```", + } + } + + command_text = "git" + + async def com_update(self, message, args, kwargs): + # with os.popen('git fetch --all') as std_in: + # await message.channel.send(std_in.read()) + # with os.popen('git symbolic-ref HEAD 2>/dev/null') as std_in: + # branch = std_in.read().replace('refs/heads/', '') + # with os.popen('git reset --hard origin/%s' % branch) as std_in: + # await message.channel.send(std_in.read()) + # await message.channel.send(message.author.mention + ", Le dépôt a été mis à jour (fetch + reset --hard).") + print("git update") diff --git a/modules/help.py b/modules/help.py index f445e84..2632819 100644 --- a/modules/help.py +++ b/modules/help.py @@ -1,81 +1,41 @@ import discord +from modules.base import BaseClass +import discord -class MainClass: - def auth(self, user, moduleName): - if user.id in self.owners: - return True - try: - self.modules[moduleName][1].authlist - except: - return True - for guild in self.client.guilds: - if guild.get_member(user.id): - for role_id in self.modules[moduleName][1].authlist: - if role_id in [r.id for r in guild.get_member(user.id).roles]: - return True +from modules.base import BaseClass - def __init__(self, client, modules, owners, prefix): - self.client = client - self.modules = modules - self.owners = owners - self.prefix = prefix - self.events = ['on_message'] # events list - self.command = "%shelp" % self.prefix # command prefix (can be empty to catch every single messages) - self.name = "Aide" - self.description = "Module d'aide" - self.interactive = True - self.color = 0x3c9653 - self.help = """\ - help list - => Affiche une liste des modules ainsi qu'une description - - help - => Affiche l'aide spécifique d'un module. - - help all - => Affiche les aides de tous les modules -""" +class MainClass(BaseClass): + name = "help" + help_active = True + help = { + "description": "Module d'aide.", + "commands": { + "`{prefix}{command} list`": "Affiche la liste de tous les modules ainsi qu'une description.", + "`{prefix}{command} `": "Renvoie l'aide spécifique à un module.", + "`{prefix}{command} all`": "Affiche l'aide de tous les modules." + } + } + color = 0x3c9653 + command_text = "help" - async def on_message(self, message): - args = message.content.lower().split(' ') - if len(args) == 2 and args[1] == 'list': - embed = discord.Embed(title="[Aide] - Liste des modules", color=self.color) - for moduleName in list(self.modules.keys()): - if self.modules[moduleName][1].interactive and \ - self.auth(message.author, moduleName): - embed.add_field(name=moduleName.capitalize(), value=self.modules[moduleName][1].description) - await message.channel.send(embed=embed) - elif len(args) == 2 and args[1] in list(self.modules.keys()) and \ - self.modules[args[1]][1].interactive and \ - self.auth(message.author, args[1]): - await message.channel.send(embed=discord.Embed(title="[{0}] - Aide".format(args[1].capitalize()), - description=self.modules[args[1]][1].help.replace( - "", self.prefix), - color=self.modules[args[1]][1].color) - ) - elif len(args) == 2 and args[1] == 'all': - async with message.channel.typing(): - for moduleName in list(self.modules.keys()): - if self.modules[moduleName][1].interactive and \ - self.auth(message.author, moduleName): - await message.channel.send( - embed=discord.Embed(title="[{0}] - Aide".format(moduleName.capitalize()), - description=self.modules[moduleName][1].help.replace("", - self.prefix), - color=self.modules[moduleName][1].color) - ) + async def com_list(self, message, args, kwargs): + embed = discord.Embed(title=_("[Aide] - Liste des modules"), color=self.color) + for moduleName in list(self.client.modules.keys()): + if self.client.modules[moduleName]["initialized_class"].help_active and \ + self.auth(message.author, moduleName): + embed.add_field( + name=moduleName.capitalize(), + value=self.client.modules[moduleName]["initialized_class"].help["description"]) + await message.channel.send(embed=embed) + + async def com_all(self, message, args, kwargs): + for name, module in self.client.modules.items(): + await module["initialized_class"].send_help(message.channel) + + async def command(self, message, args, kwargs): + if len(args) and args[0] in self.client.modules.keys(): + await self.client.modules[args[0]]["initialized_class"].send_help(message.channel) else: - await self.modules['help'][1].send_help(message.channel, self) - - async def send_help(self, channel, module): - moduleName = None - for name, list_module_instance in self.modules.items(): - if module == list_module_instance[1]: - moduleName = name - break - await channel.send(embed=discord.Embed(title="[{0}] - Aide".format(moduleName.capitalize()), - description=self.modules[moduleName][1].help.replace("", - self.prefix), - color=self.modules[moduleName][1].color)) + await self.send_help(message.channel) diff --git a/modules/modules.py b/modules/modules.py index 8be4907..9ed5c55 100644 --- a/modules/modules.py +++ b/modules/modules.py @@ -1,183 +1,101 @@ -import importlib import os -import random -import shutil -import time import discord +from aiohttp import ClientSession -temp_dir = "temp_load" -moduleFiles = "modules" +from .base import BaseClass -class MainClass: - def __init__(self, client, modules, owners, prefix): - if os.path.exists("storage/" + moduleFiles + "/" + temp_dir): - shutil.rmtree("storage/" + moduleFiles + "/" + temp_dir) - if not os.path.exists("storage/" + moduleFiles): - os.mkdir("storage/" + moduleFiles) - os.mkdir("storage/" + moduleFiles + "/" + temp_dir) - self.states = {} - for moduleName in os.listdir('modules'): - if moduleName.endswith(".py"): - self.states.update({moduleName[:-3:]: 'not loaded'}) - for moduleName in list(modules.keys()): - if len(modules[moduleName]) == 2: - self.states.update({moduleName: 'initialized'}) - self.client = client - self.modules = modules - self.owners = owners - self.prefix = prefix - self.events = ['on_message', 'on_ready'] # events list - self.command = "%smodule" % self.prefix # command prefix (can be empty to catch every single messages) +class MainClass(BaseClass): + name = "modules" + super_users = [431043517217898496] + color = 0x000000 + help_active = True + help = { + "description": "Manage bot modules.", + "commands": { + "`{prefix}{command} list`": "List of available modules.", + "`{prefix}{command} enable `": "Enable module ``.", + "`{prefix}{command} disable `": "Disable module ``.", + } + } + command_text = "modules" - self.name = "Modules" - self.description = "Module de gestion des modules" - self.interactive = True - self.authlist = [549662392120901633] - self.color = 0x8f3196 - self.help = """\ - modules list - => Liste les modules ainsi que leurs états - - modules enable - => Charge et active le / les module(s) spécifié(s) - - modules disable - => Désactive et décharge le / les module(s) spécifié(s) - - modules reload - => Désactive, décharge, puis recharge et réactive le / les module(s) spécifié(s) - - => - ==> Unique module ou liste de module séparés par des virgules -""" - self.states.update({'modules': 'initialized'}) + def __init__(self, client): + super().__init__(client) + self.storage.mkdir("modules") - async def on_message(self, message): - error = None - args = message.content.split(" ") - if len(args) == 2 and args[1] == 'list': - await message.channel.send(embed=discord.Embed(title="[Modules] - Modules list", - description="```PYTHON\n{0}```".format( - str(self.states).replace(',', '\n,')) - ) - ) - elif len(args) == 3 and args[1] in ['enable', 'disable', 'reload']: - if args[1] == 'enable': - for moduleName in args[2].split(','): - if moduleName + '.py' in os.listdir('modules'): - try: - self.enable_module(moduleName) - await message.channel.send( - message.author.mention + ", le module {0} a été activé".format(moduleName)) - except Exception as e: - error = e - await message.channel.send( - message.author.mention + ", le module {0} **n'a pas pu être activé**".format( - moduleName)) - else: - await message.channel.send( - message.author.mention + ", le module {0} n'existe pas.".format(moduleName)) - elif args[1] == 'disable': - for moduleName in args[2].split(','): - if moduleName == 'modules': - await message.channel.send( - message.author.mention + ", le module {0} ne peut pas être désactivé car il est nécéssaire " - "pour gérer les modules.".format(moduleName) - ) - else: - if moduleName + '.py' in os.listdir('modules'): - self.unload_module(moduleName) - await message.channel.send( - message.author.mention + ", le module {0} a été désactivé.".format(moduleName)) - else: - await message.channel.send( - message.author.mention + ", le module {0} n'existe pas.".format(moduleName)) - elif args[1] == 'reload': - for moduleName in args[2].split(','): - if moduleName == 'modules': - await message.channel.send( - message.author.mention + ", le module {0} ne peut pas être rechargé car il est nécéssaire " - "pour gérer les modules.".format(moduleName) - ) - else: - if moduleName in self.modules.keys(): - self.unload_module(moduleName) - await message.channel.send( - message.author.mention + ", le module {0} a été désactivé.".format(moduleName)) - else: - await message.channel.send( - message.author.mention + ", le module {0} n'est pas chargé.".format(moduleName)) - if moduleName + '.py' in os.listdir('modules'): - try: - self.enable_module(moduleName) - await message.channel.send( - message.author.mention + ", le module {0} a été activé".format(moduleName)) - except Exception as e: - error = e - await message.channel.send( - message.author.mention + ", le module {0} **n'a pas pu être activé**".format( - moduleName) - ) - else: - await message.channel.send( - message.author.mention + ", le module {0} n'existe pas.".format(moduleName) - ) + def get_all_modules(self): + all_items = os.listdir("modules") + modules = [] + for item in all_items: + if item not in ["__init__.py", "base.py", "__pycache__", "dummy"]: + if os.path.isfile(os.path.join("modules", item)): + modules.append(item[:-3]) + else: + modules.append(item) + return set(modules) - else: - await self.modules['help'][1].send_help(message.channel, self) - if error: - raise error + 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 on_ready(self): - error = None - for fileName in os.listdir('modules'): - try: - self.load_module(fileName[:-3:]) - self.init_module(fileName[:-3:]) - except Exception as e: - error = e - if error: - raise error + 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, [], []) - def enable_module(self, moduleName): - self.load_module(moduleName) - self.init_module(moduleName) + 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) - def load_module(self, moduleName): - if moduleName + ".py" in os.listdir('modules'): - if moduleName not in list(self.states.keys()) or self.states[moduleName] == 'not loaded': - try: - new_file_name = str( - "storage/" + - moduleFiles + "/" + - temp_dir + "/" + moduleName + "-%s.py") % random.randint(1, 100000000000000000000000000000000) - shutil.copy2("modules/%s.py" % moduleName, new_file_name) - time.sleep(0.1) - self.modules.update({moduleName: [importlib.import_module(new_file_name.replace('/', '.')[:-3:])]}) - print("Module {0} chargé.".format(moduleName)) - self.states[moduleName] = 'loaded' - except: - print("[ERROR] Le module {0} n'a pas pu être chargé.".format(moduleName)) - self.unload_module(moduleName) - raise + async def com_download(self, message, args, kwargs): + if len(args) == 3: + async with ClientSession() as session: + async with session.get("http://127.0.0.1:5000/api/modules/"+args[1]+"/"+args[2]) as response: + if response.status != 200: + await message.channel.send(f"Erreur lors du téléchargement de {args[1]}") + return + with self.storage.open(os.path.join("modules", args[1]+".zip"), "wb") as file: + file.write(await response.read()) - def init_module(self, moduleName): - if moduleName + ".py" in os.listdir('modules'): - if self.states[moduleName] == 'loaded': - try: - self.modules[moduleName].append( - self.modules[moduleName][0].MainClass(self.client, self.modules, self.owners, self.prefix)) - print("Module {0} initialisé.".format(moduleName)) - self.states[moduleName] = 'initialized' - except: - print("[ERROR] Le module {0} n'a pas pu être initialisé.".format(moduleName)) - self.unload_module(moduleName) - raise - - def unload_module(self, moduleName): - if moduleName + ".py" in os.listdir('modules'): - self.states[moduleName] = 'not loaded' - self.modules.pop(moduleName, None) - print("Module {0} déchargé.".format(moduleName)) diff --git a/modules/restart.py b/modules/restart.py index 14acfae..0bc5be9 100644 --- a/modules/restart.py +++ b/modules/restart.py @@ -1,26 +1,26 @@ -class MainClass: - def __init__(self, client, modules, owners, prefix): - self.client = client - self.modules = modules - self.owners = owners - self.prefix = prefix - self.events = ['on_message'] # events list - self.command = "%srestart" % self.prefix # command prefix (can be empty to catch every single messages) +from .base import BaseClass - self.name = "Restart" - self.description = "Module gérant les redémarrages du bot" - self.interactive = True - self.authlist = [] - self.color = 0x000000 - self.help = """\ - restart -""" - async def on_message(self, message): - args = message.content.split(" ") - if args[0] == '%srestart' % self.prefix: - if 'py' in args: - await message.channel.send(message.author.mention + ", Le bot va redémarrer...") - await self.client.logout() - else: - await self.modules['help'][1].send_help(message.channel, self) +class MainClass(BaseClass): + name = "restart" + + super_users = [431043517217898496] + + color = 0x000000 + + help_active = True + help = { + "description": "Gestion des redémarrages du bot", + "commands": { + "`{prefix}{command} py`": "Redémarre le bot.", + } + } + + command_text = "restart" + + async def command(self, message, args, kwargs): + await message.channel.send("{mention}, vous devez utiliser `{prefix}restart py` pour redémarer." + .format(prefix=self.client.config["prefix"], mention=message.author.mention)) + + async def com_py(self, message, args, kwargs): + await self.client.logout()