diff --git a/.env b/.env new file mode 100644 index 0000000..102816c --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DISCORD_TOKEN="NTUwMDkxOTAyMDY2ODg0NjA4.XPKt2Q.iSlsETqAT-5jW6KgbhkNMIvN0WY" diff --git a/Pipfile b/Pipfile index 1da9319..08cc411 100644 --- a/Pipfile +++ b/Pipfile @@ -1,15 +1,23 @@ [[source]] + url = "https://pypi.org/simple" verify_ssl = true name = "pypi" + [packages] + packaging = "*" discord-py = {extras = ["voice"],git = "https://github.com/Rapptz/discord.py",ref = "84c1eac62a775a37b03bd0971b221b0c50724630"} aiohttp = "*" aiofiles = "*" +lupa = "*" + [dev-packages] + + [requires] + python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 8446c59..aa14c82 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e5ce2d692a19fe61098fd4a82500439d23c83afcc1f305cbe84a1c774fcf3505" + "sha256": "d554d2ea6c77f139d26001582b17e7ca5430ed151ab8e92f8f7354c8d59438d4" }, "host-environment-markers": { "implementation_name": "cpython", @@ -11,7 +11,7 @@ "platform_python_implementation": "CPython", "platform_release": "4.19.27-gentoo-r1", "platform_system": "Linux", - "platform_version": "#1 SMP Wed May 1 11:50:20 CEST 2019", + "platform_version": "#1 SMP Sat Jun 1 19:49:57 CEST 2019", "python_full_version": "3.7.3", "python_version": "3.7", "sys_platform": "linux" @@ -65,6 +65,10 @@ "markers": "python_version < '3.7'", "version": "==1.1.0" }, + "lupa": { + "hashes": [], + "version": "==1.8" + }, "multidict": { "hashes": [], "version": "==4.5.2" diff --git a/main.py b/main.py index 3098e0b..0a19f6f 100644 --- a/main.py +++ b/main.py @@ -17,7 +17,7 @@ import discord from packaging.version import Version from errors import IncompatibleModule -from modules.base import BaseClass +from modules.base import BaseClassPython __version__ = "0.1.0" @@ -408,40 +408,24 @@ class Communication(asyncio.Protocol): def connection_lost(self, exc): print('%s: connection lost: %s' % (self.name, exc)) - async def parse_set_param(self, data): - values = data[8:].split("$¤$") - for value in values: - value = value.replace(r"\$¤$", "$¤$") - await self.client.dispatch("setparam", - value.split("$=$")[0].replace(r"\$=$", "$=$"), - value.split("$=$")[1].replace(r"\$=$", "$=$")) - - -# os.path.join("tmp", os.path.dirname(os.path.realpath(__file__)) + ".sock") communication = Communication(client1) async def start_bot(): - await client1.start('TOKEN', max_messages=500000) + await client1.start(os.environ.get("DISCORD_TOKEN"), max_messages=500000) -def communication_execption_handler(loop, context): +def execption_handler(loop, context): print('%s: %s' % ('Connection', context['exception'])) traceback.print_exc() -async def start_communication(): - pass - # loop.run_until_complete(f) - # print('Server running on %s forwarding to %s' % (proxy_in_addr, proxy_out_addr)) - - -print(os.path.join("/tmp", os.path.dirname(os.path.realpath(__file__)) + ".sock")) +print(os.path.join("/tmp", os.path.dirname(os.path.realpath(__file__))) + ".sock") loop = asyncio.get_event_loop() loop.add_signal_handler(signal.SIGINT, loop.stop) -loop.set_exception_handler(communication_execption_handler) +loop.set_exception_handler(execption_handler) t = loop.create_unix_server(Communication, path=os.path.join("/tmp", os.path.dirname(os.path.realpath(__file__)) + ".sock")) loop.run_until_complete(t) diff --git a/modules/base/BaseLua.py b/modules/base/BaseLua.py new file mode 100644 index 0000000..ecfc589 --- /dev/null +++ b/modules/base/BaseLua.py @@ -0,0 +1,48 @@ +"""Base class for module, never use directly !!!""" +import asyncio +import sys +import pickle +import traceback + +import discord +import lupa + +from storage import FSStorage +import storage.path as path + + +class BaseClassLua: + """Base class for all modules, Override it to make submodules""" + name = "" + help = { + "description": "", + "commands": { + + } + } + help_active = False + color = 0x000000 + command_text = None + super_users = [] + authorized_roles = [] + + def __init__(self, client): + """Initialize module class + + Initialize module class, always call it to set self.client when you override it. + + :param client: client instance + :type client: NikolaTesla""" + self.client = client + self.storage = FSStorage(path.join(self.client.base_path, self.name)) + if not self.storage.isdir(path.join("storage", self.name)): + self.storage.makedirs(path.join("storage", self.name), exist_ok=True) + # Get lua globals + self.lua = lupa.LuaRuntime(unpack_returned_tuples=True) + self.luaMethods = self.lua.eval("require \"main\"") + + def dispatch(self, event, *args, **kwargs): + print(self.luaMethods) + print(self.luaMethods.__dict__) + print(dict(self.luaMethods)) + diff --git a/modules/base/BasePython.py b/modules/base/BasePython.py new file mode 100644 index 0000000..c2750c1 --- /dev/null +++ b/modules/base/BasePython.py @@ -0,0 +1,209 @@ +"""Base class for module, never use directly !!!""" +import asyncio +import sys +import pickle +import traceback + +import discord + +from storage import FSStorage +import storage.path as path + + +class BaseClassPython: + """Base class for all modules, Override it to make submodules""" + name = "" + help = { + "description": "", + "commands": { + + } + } + help_active = False + color = 0x000000 + command_text = None + super_users = [] + authorized_roles = [] + + def __init__(self, client): + """Initialize module class + + Initialize module class, always call it to set self.client when you override it. + + :param client: client instance + :type client: NikolaTesla""" + self.client = client + self.storage = FSStorage(path.join(self.client.base_path, self.name)) + if not self.storage.isdir(path.join("storage", self.name)): + self.storage.makedirs(path.join("storage", self.name), exist_ok=True) + + 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) + + 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 dispatch(self, event, *args, **kwargs): + # Method to call + method = 'on_' + event + try: + # Try to get coro, if not exists pass without raise an error + coro = getattr(self, method) + except AttributeError: + pass + else: + # Run event + asyncio.ensure_future(self._run_event(coro, method, *args, **kwargs), loop=self.loop) + + async def _run_event(self, coro, event_name, *args, **kwargs): + # Run event + try: + await coro(*args, **kwargs) + except asyncio.CancelledError: + # If function is cancelled pass silently + pass + except Exception: + try: + # Call error function + await self.on_error(event_name, *args, **kwargs) + except asyncio.CancelledError: + # If error event is canceled pass silently + pass + + async def on_error(self, event_method, *args, **kwargs): + # Basic error handler + print('Ignoring exception in {}'.format(event_method), file=sys.stderr) + traceback.print_exc() diff --git a/modules/base/__init__.py b/modules/base/__init__.py index c52e06f..3549db6 100644 --- a/modules/base/__init__.py +++ b/modules/base/__init__.py @@ -1,209 +1,2 @@ -"""Base class for module, never use directly !!!""" -import asyncio -import sys -import pickle -import traceback - -import discord - -from storage import FSStorage -import storage.path as path - - -class BaseClass: - """Base class for all modules, Override it to make submodules""" - name = "" - help = { - "description": "", - "commands": { - - } - } - help_active = False - color = 0x000000 - command_text = None - super_users = [] - authorized_roles = [] - - def __init__(self, client): - """Initialize module class - - Initialize module class, always call it to set self.client when you override it. - - :param client: client instance - :type client: NikolaTesla""" - self.client = client - self.storage = FSStorage(path.join(self.client.base_path, self.name)) - if not self.storage.isdir(path.join("storage", self.name)): - self.storage.makedirs(path.join("storage", self.name), exist_ok=True) - - 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) - - 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 dispatch(self, event, *args, **kwargs): - # Method to call - method = 'on_' + event - try: - # Try to get coro, if not exists pass without raise an error - coro = getattr(self, method) - except AttributeError: - pass - else: - # Run event - asyncio.ensure_future(self._run_event(coro, method, *args, **kwargs), loop=self.loop) - - async def _run_event(self, coro, event_name, *args, **kwargs): - # Run event - try: - await coro(*args, **kwargs) - except asyncio.CancelledError: - # If function is cancelled pass silently - pass - except Exception: - try: - # Call error function - await self.on_error(event_name, *args, **kwargs) - except asyncio.CancelledError: - # If error event is canceled pass silently - pass - - async def on_error(self, event_method, *args, **kwargs): - # Basic error handler - print('Ignoring exception in {}'.format(event_method), file=sys.stderr) - traceback.print_exc() +from .BasePython import BaseClassPython +from .BaseLua import BaseClassLua \ No newline at end of file diff --git a/modules/modules/__init__.py b/modules/modules/__init__.py index cd0fb4d..c357b6f 100644 --- a/modules/modules/__init__.py +++ b/modules/modules/__init__.py @@ -2,11 +2,11 @@ import os import discord -from modules.base import BaseClass +from modules.base import BaseClassPython from modules.modules.api import Api -class MainClass(BaseClass): +class MainClass(BaseClassPython): name = "modules" command_text = "modules" diff --git a/modules/test_lua/__init__.py b/modules/test_lua/__init__.py new file mode 100644 index 0000000..49ff50e --- /dev/null +++ b/modules/test_lua/__init__.py @@ -0,0 +1,5 @@ +from modules.base import BaseClassLua + + +class MainClass(BaseClassLua): + pass diff --git a/modules/test_lua/main.lua b/modules/test_lua/main.lua new file mode 100644 index 0000000..223b64e --- /dev/null +++ b/modules/test_lua/main.lua @@ -0,0 +1,8 @@ +main = {} + +function main.on_message(discord, message) + print("I LOVE LUA") + print(message.content) +end + +return main \ No newline at end of file