diff --git a/src/core/__init__.py b/src/core/__init__.py new file mode 100644 index 0000000..e34e5d9 --- /dev/null +++ b/src/core/__init__.py @@ -0,0 +1 @@ +from .bot import Bot diff --git a/src/core/bot.py b/src/core/bot.py new file mode 100644 index 0000000..8f26dc9 --- /dev/null +++ b/src/core/bot.py @@ -0,0 +1,179 @@ +from pathlib import Path + +import requests +import vk +from loguru import logger + +import modules +from modules import config, is_new_version +from modules.perms import Permissions +from .hosts import Hosts + + +class Bot: + + def __init__(self): + self.hosts: Hosts + self.perms: Permissions + self._test() + self.vk = vk.API(access_token=config["vk_token"], v=5.199) + print("Получение GroupID..", end="") + self.group_id = self.vk.groups.getById()['groups'][0]['id'] + with open(config["help_file"], encoding="utf-8") as f: + self.help_message = f.read() + logger.info(f"[BOT] ID группы: {self.group_id}") + + def _test(self): + Permissions.perm_file = Path(config["perms_file"]) + self.perms = Permissions.load() + self.hosts = Hosts.load() + # Check token + if not config["vk_token"]: + logger.error("Токен ВК не найден.") + modules.enter_to_exit() + + def get_lp_server(self): + lp = self.vk.groups.getLongPollServer(group_id=self.group_id) + return lp.get('server'), lp.get('key'), lp.get('ts') + + def write(self, peer_id, message): + if len(message) > 4095: + messages = (len(message) // 4095) + for i in range(1, messages + 1): + if i > 30: + logger.error("[BOT] Сообщение слишком длинное...") + return + self.write(peer_id, message[:4095 * i]) + else: + self.vk.messages.send(message=message, peer_id=peer_id, random_id=0) + + def rcon_cmd_handle(self, cmd, from_id, peer_id, _write=True, allow=False): + """Проверка прав и выполнение RCON команды""" + if allow: + role = "console" + else: + allow, role = self.perms.is_allowed(from_id, cmd.split()[0]) + if allow: + answer, _ = self.hosts.rcon(cmd) + if not answer: + answer = "Выполнено без ответа." + logger.info(f"[BOT] User: {from_id}({role}) in Chat: {peer_id} use RCON cmd: \"{cmd}\", " + f"with answer: \"{answer}\"") + if _write: + self.write(peer_id, answer) + else: + return answer + else: + logger.info(f"[BOT] User: {from_id}({role}) in Chat: {peer_id} no have rights RCON cmd: \"{cmd}\".") + if self.perms.no_rights: # Если есть текст + self.write(peer_id, self.perms.no_rights) + + def _bot_handle(self, message): + from_id = message['from_id'] + if self.perms.is_allowed(from_id, "bot"): + peer_id = message['peer_id'] + text = message['text'] + logger.info(f"[BOT] {peer_id}:{from_id}:{text}") + tsplit = text.split(" ") + cmds = ("Доступные команды:\n" + " .bot help - Вывести это сообщение.\n" + " .bot info - Выводит краткую информацию о боте.\n" + " .bot hosts list - Список доступных хостов\n" + " .bot hosts reload - Перезагружает hosts.yml\n" + # " .bot perms user [add | del] - не реализовано \n" + # " .bot perms list - Выводит список групп \n" + " .bot perms reload - Перезагружает permissions.yml") + if len(tsplit) == 1: + self.write(peer_id, cmds) + return + match tsplit[1]: + case "hosts": + match tsplit[2] if len(tsplit) > 2 else None: + case "list": + s = "" + for host in self.hosts.hosts: + ping = 0 + r, e = self.hosts.mine(host, True) + if not e: + ping = r.latency + _, e = self.hosts.rcon("list", host, True) + + meta = self.hosts._hosts_meta[host] + name = meta.get("name") + rcon_ok = meta.get('rcon_ok') + mine_ok = meta.get('mine_ok') + if (not rcon_ok and meta['rcon'] > 0) or (not mine_ok and meta['online'] > 0): + s += f"\nㅤ⛔ {host} ({name})" + else: + s += f"\nㅤ✅ {host} ({name})" + if "-a" in tsplit or "--all" in tsplit: + s += (f":\n" + f"ㅤㅤimportant: {meta['important']}\n" + f"ㅤㅤrcon_default: {meta['rcon'] == 1}\n" + f"ㅤㅤmine_default: {meta['online'] == 1}\n" + f"ㅤㅤrcon: {not bool(e)}\n" + f"ㅤㅤping: {ping:.4f}ms\n" + f"ㅤㅤrcon_ok: {rcon_ok}\n" + f"ㅤㅤmine_ok: {mine_ok}") + self.write(peer_id, "Список хостов:" + s) + case "reload": + self.hosts = Hosts.load() + self.write(peer_id, "hosts.yml - Загружен") + case _: + self.write(peer_id, ".bot hosts [list | reload]") + case "perms": + match tsplit[2] if len(tsplit) > 2 else None: + case "reload": + self.perms = Permissions.load() + self.write(peer_id, "permissions.yml - Загружен") + case _: + self.write(peer_id, ".bot perms [reload]") + case "info": + self.write(peer_id, f"RconVkBot\n" + f"Версия бота: {modules.__version__}, последняя: {not is_new_version}") + case _: + self.write(peer_id, cmds) + + def message_handle(self, message): + from_id = message['from_id'] + peer_id = message['peer_id'] + text = message['text'] + match text: + case i if i.startswith(".rcon "): + self.rcon_cmd_handle(i[6:], from_id, peer_id) + case i if i.startswith(".bot"): + self._bot_handle(message) + case "!help": + logger.info(f"[BOT] {peer_id}:{from_id}:{text}") + self.write(peer_id, self.help_message) + case "!online": + logger.info(f"[BOT] {peer_id}:{from_id}:{text}") + server, _ = self.hosts.mine() + players = server.players + self.write(peer_id, f"На сервере сейчас {players.online}/{players.max}") + case "!id": + logger.info(f"[BOT] {peer_id}:{from_id}:{text}") + self.write(peer_id, + f"Твой ID: {from_id}\n" + f"Роль: {self.perms.get_role(from_id)}\n" + f"Ник: {self.perms.get_nick(from_id)}") + + def listen(self): + server, key, ts = self.get_lp_server() + logger.info("[BOT] Начинаю получать сообщения..") + while True: + lp = requests.get(f'{server}?act=a_check&key={key}&ts={ts}&wait=25').json() + try: + if lp.get('failed') is not None: + key = self.get_lp_server()[1] + if ts != lp.get('ts') and lp.get('updates'): + updates = lp['updates'][0] + if updates['type'] == "message_new": + self.message_handle(updates['object']['message']) + ts = lp.get('ts') + except Exception as i: + ts = lp.get('ts') + logger.exception(i) + + def stop(self): + self.hosts.unload() diff --git a/src/core/hosts.py b/src/core/hosts.py new file mode 100644 index 0000000..be9b0fc --- /dev/null +++ b/src/core/hosts.py @@ -0,0 +1,124 @@ +import os +import re +from pathlib import Path + +from loguru import logger +from mcrcon import MCRcon +from mcstatus import JavaServer, BedrockServer + +from modules import yaml, raw_config_hosts, enter_to_exit + + +class Hosts: + hosts_config = Path("config/hosts.yml") + + def __init__(self, **kwargs): + self._hosts = kwargs["hosts"] + self.hosts = set() + self._hosts_rcon = {} + self._hosts_mine = {} + self._hosts_meta = {} + self._connect() + logger.info("[HOSTS] Хосты загружены") + + def rcon(self, cmd: str, server: str = "default", update=False) -> tuple[str | None, Exception | None]: + if not update and not self._hosts_meta[server].get('rcon_ok', True): + return ("Сервер в листе нерабочих.\nЕсли это не так, обновить можно этой командой\n.bot hosts list", + Exception("Сервер в листе нерабочих")) + if server not in self._hosts_rcon: + return "Сервер не найден", Exception("Сервер не найден") + try: + with MCRcon(**self._hosts_rcon[server]) as mcr: + text = mcr.command(cmd) + self._hosts_meta[server]['rcon_ok'] = True + return re.sub(r'§.', '', text), None + except Exception as e: + self._hosts_meta[server]['rcon_ok'] = False + logger.error(f"[RCON] Сервер: {server}; Команда: {cmd}") + logger.error(e) + return f"error: \"{e}\"", e + + def mine(self, server: str = "default", update=False) -> tuple[JavaServer.lookup, Exception | None]: + if not update and not self._hosts_meta[server].get('mine_ok', True): + return ("Сервер в листе нерабочих.\nЕсли это не так, обновить можно этой командой\n.bot hosts list", + Exception("Сервер в листе нерабочих")) + if server not in self._hosts_mine: + return None, Exception("Сервер не найден") + s = self._hosts_mine[server] + try: + if self._hosts_meta[server]["java"]: + srv = JavaServer.lookup(f'{s["host"]}:{s["port"]}').status() + else: + # noinspection PyArgumentList + srv = BedrockServer.lookup(f'{s["host"]}:{s["port"]}').status() + self._hosts_meta[server]['mine_ok'] = True + return srv, None + except Exception as e: + self._hosts_meta[server]['mine_ok'] = False + logger.error(f"[MINE] Сервер не отвечает: {server} {s}") + logger.exception(e) + self._hosts_meta["connected"] = False + return None, e + + def _connect(self) -> None: + if self._hosts is None or len(self._hosts) == 0: + logger.error("[HOSTS] Не найдено ни одного хоста.") + enter_to_exit() + + self._hosts_meta['default'] = {} + + # Test RCON + for name in self._hosts: + self.hosts.add(name) + server = self._hosts[name] + meta = self._hosts_meta[name] = server['meta'] + if meta['rcon'] > 0: + rcon = self._hosts_rcon[name] = server['rcon'] + print(f"Проверка RCON {name}..", end="") + srv, e = self.rcon("list", name) + if srv: + if meta['rcon'] == 1: + if self._hosts_rcon.get('default'): + logger.warning(f"[RCON] hosts.{name}.meta.rcon = 1 - Хотя уже есть дефолтный.") + self._hosts_rcon['default'] = rcon + self._hosts_meta['default']['rcon_ok'] = True + logger.info(f"[RCON] {name} доступен.") + else: + if meta["important"]: + logger.error(f"[RCON] Важный хост не доступен: {name}") + enter_to_exit() + if meta["online"] > 0: + mine = self._hosts_mine[name] = server['mine'] + print(f"Проверка MINE {name}..", end="") + srv, e = self.mine(name) + if srv: + if meta['online'] == 1: + if self._hosts_mine.get('default'): + logger.warning(f"[RCON] hosts.{name}.meta.online = 1 - Хотя уже есть дефолтный.") + self._hosts_mine['default'] = mine + self._hosts_meta['default']['java'] = meta['java'] + self._hosts_meta['default']['mine_ok'] = True + players = srv.players + logger.info(f"[MINE] {name} доступен. {players.online}/{players.max} {srv.latency:.3f}ms") + else: + if meta["important"]: + logger.error(f"[MINE] Важный хост не доступен: {name}") + enter_to_exit() + + @classmethod + def load(cls) -> "Hosts": + if os.path.exists(cls.hosts_config): + data = yaml.load(cls.hosts_config) + if not data: + os.remove(cls.hosts_config) + return cls.load() + else: + data = yaml.load(raw_config_hosts) + with open(cls.hosts_config, mode="w", encoding="utf-8") as f: + yaml.dump(data, f) + + logger.info(f"[HOSTS] {cls.hosts_config} - загружен") + return Hosts(**data) + + def unload(self): + return diff --git a/src/main.py b/src/main.py index 7dbe84f..d051532 100644 --- a/src/main.py +++ b/src/main.py @@ -1,40 +1,19 @@ import sys -from pathlib import Path from loguru import logger -from modules import config, rcon, get_server_status, enter_to_exit -from modules.bot import Bot -from modules.perms import Permissions +from core import Bot +from modules import enter_to_exit if __name__ == '__main__': - Permissions.perm_file = Path(config.permissions_file) - perms = Permissions.load() - # Check token - if not config.vk.token: - logger.error("Токен ВК не найден.") - enter_to_exit() - bot = Bot(perms) - # Test RCON - print("Проверка RCON..", end="") - if rcon("list").startswith("Rcon error"): - logger.error("RCON не отвечает. Проверьте блок \"rcon\" в config.json") - enter_to_exit() - logger.info("RCON доступен.") - # Test Minecraft Server - print("Проверка сервера..", end="") - try: - st = get_server_status() - players = st.players - logger.info(f"Сервер доступен. Пинг: {st.latency:.3f}ms, Онлайн: {players.online}/{players.max}") - except Exception as e: - logger.exception(e) - logger.info("Сервер не отвечает. Проверьте блок \"minecraft\" в config.json") - enter_to_exit() + bot = Bot() try: bot.listen() except KeyboardInterrupt: + logger.info("Exited.") sys.exit(0) except Exception as e: logger.exception(e) enter_to_exit() + finally: + bot.stop() diff --git a/src/modules/__init__.py b/src/modules/__init__.py index 1d87c3f..87b0545 100644 --- a/src/modules/__init__.py +++ b/src/modules/__init__.py @@ -1,15 +1,11 @@ import glob -import json import os -import re import sys import zipfile -from collections import namedtuple from datetime import datetime import requests from loguru import logger -from mcrcon import MCRcon from ruamel.yaml import YAML yaml = YAML() @@ -18,24 +14,118 @@ IN_DOCKER = "DOCKER_CONTAINER" in os.environ __version__ = '1.3.1' -raw_config = """\ -{ - "vk": { - "token": "", - "help_file": "help_message.txt" - }, - "rcon": { - "host": "127.0.0.1", - "port": 25575, - "password": "P@ssw0rd" - }, - "minecraft": { - "host": "127.0.0.1", - "port": 25565, - "java": true - }, - "permissions_file": "permissions.yml" -}""" +raw_config_main = """\ +vk_token: "" +help_file: config/help_message.txt +perms_file: config/permissions.yml +hosts_file: config/hosts.yml +""" + +raw_config_perms = """\ +noRole: Нет роли +noRights: Нет прав # null для отключения +noNick: Не указан # Используется для !id + +# Таблица соответствия vkID к нику в Майнкрафте +# Ник будет передаваться в плагины (Плагины бота) +nicks: + 370926160: Rick + 583018016: SantaSpeen + +perms: + admin: # Имя группы + name: Админ # Имя группы, которое будет отображаться в боте + ids: # вк ИД входящих в состав группы + - 370926160 + parent: # Наследование прав + - helper + allow: # Какие команды разрешены, "*" - все + - '*' + helper: + name: Хелпер + ids: + - 583018016 + allow: + - bot.rcon.* # См. host.yml + - say + - mute + - warn + default: + name: Игрок + allow: + - bot.online.* # См. host.yml + - bot.history.* # См. host.yml + +""" + +raw_config_hosts = """\ +hosts: + survival: # Название сервера (имя), может быть любым, может быть сколько угодно + meta: + name: Выживание # Это имя будет выводиться в ответе, или статистике (имя для бота) + java: true # Это JAVA сервер? + important: true # Если да, то бот не включится, если не подключится + # 0 - выключен + # 1 - Доступно без имени (!! Такое значение может быть только у 1 хоста !!) (.rcon ) + # 2 - Доступно с именем (.rcon ) + # Разрешение: bot.rcon.; bot.online.; bot.history. + # При запуске бота будет проверка доступности всего + rcon: 2 # RCON будет доступен по команде .rcon lobby (разрешение: bot.rcon.lobby) + online: 2 # !online будет доступен по команде !online lobby (разрешение: bot.online.lobby) + history: 2 # !history будет доступен по команде !history lobby (разрешение: bot.history.lobby) + rcon: # RCON подключение + host: 192.168.0.31 + port: 15101 + password: rconPass1 + mine: # Minecraft подключение (нужно для !online и !history) + host: 192.168.0.31 + port: 15001 + + lobby: + meta: + name: Лобби + important: true + java: true + rcon: 1 + online: 2 + history: 2 + rcon: + host: 192.168.0.31 + port: 15100 + password: rconPass2 + mine: + host: 192.168.0.31 + port: 15000 + + devlobby: + meta: + name: Лобби + important: false + java: true + rcon: 2 + online: 2 + history: 0 + rcon: + host: 192.168.0.31 + port: 15108 + password: rconPass3 + mine: + host: 192.168.0.31 + port: 15008 + + proxy-local: + meta: + name: Proxy-Local + java: true + important: true + rcon: 0 + online: 1 + history: 1 + rcon: null + mine: + host: 192.168.0.31 + port: 15009 +""" raw_help = """\ Тебе не нужна помощь, ты и так беспомощный, кожаный ублюдок. Так уж и быть, подскажу пару команд... @@ -44,6 +134,11 @@ raw_help = """\ Бот сделан кожанным петухом - админом, все вопросы к нему, я не причём. """ +config_dir = "./config/" +config_file_main = config_dir + "bot.yml" +if not os.path.exists(config_dir): + os.makedirs(config_dir) + def init_logger(): log_file = "./logs/latest.log" @@ -71,45 +166,21 @@ def init_logger(): init_logger() -if not os.path.exists("config.json"): - logger.info("Создание: config.json...") - with open("config.json", "w") as f: - f.write(raw_config) +if not os.path.exists(config_file_main): + logger.info(f"Создание: {config_file_main}...") + c = yaml.load(raw_config_main) + with open(config_file_main, "w") as f: + yaml.dump(c, f) -with open('config.json') as f: - config = json.load(f, object_hook=lambda x: namedtuple('X', x.keys())(*x.values())) +with open(config_file_main) as f: + config = yaml.load(f) logger.info("Запуск..") -if not os.path.exists(config.vk.help_file): - logger.info(f"Создание: {config.vk.help_file}...") +if not os.path.exists(config["help_file"]): + logger.info(f"Создание: {config["help_file"]}...") with open(config.vk.help_file, "w", encoding="utf-8") as f: f.write(raw_help) -if config.minecraft.java: - from mcstatus import JavaServer as MineServer -else: - from mcstatus import BedrockServer as MineServer - -host = config.rcon.host -port = config.rcon.port -password = config.rcon.password - - -def rcon(cmd): - try: - with MCRcon(host, password, port) as mcr: - text = mcr.command(cmd) - return re.sub(r'§.', '', text) - except Exception as e: - logger.error(f"[RCON] ERROR with command: {cmd}") - logger.exception(e) - return f"Rcon error: {e}" - - -def get_server_status(): - server = MineServer.lookup(config.minecraft.host, config.minecraft.port) - return server.status() - def enter_to_exit(exit_code=1): logger.info("Выход..") @@ -127,8 +198,8 @@ def new_version(): if ver and ver != __version__: logger.info("Обнаружена новая версия: {} -> {}", __version__, ver) return True - except: - logger.error("Не получилось проверить обновления.") + except Exception as e: + logger.error(f"Не получилось проверить обновления: {e}") else: logger.info("У вас актуальная версия") return False diff --git a/src/modules/bot.py b/src/modules/bot.py deleted file mode 100644 index 1a5b337..0000000 --- a/src/modules/bot.py +++ /dev/null @@ -1,121 +0,0 @@ -import requests -import vk -from loguru import logger - -import modules -from modules import config, rcon, get_server_status, is_new_version -from modules.perms import Permissions - - -class Bot: - - def __init__(self, perms: Permissions): - self.perms: Permissions = perms - self.vk = vk.API(access_token=config.vk.token, v=5.199) - print("Получение GroupID..", end="") - self.group_id = self.vk.groups.getById()['groups'][0]['id'] - with open('help_message.txt', encoding="utf-8") as f: - self.help_message = f.read() - logger.info(f"[BOT] ID группы: {self.group_id}") - - def get_lp_server(self): - lp = self.vk.groups.getLongPollServer(group_id=self.group_id) - return lp.get('server'), lp.get('key'), lp.get('ts') - - def write(self, peer_id, message): - if len(message) > 4095: - messages = (len(message) // 4095) - for i in range(1, messages + 1): - if i > 30: - logger.error("[BOT] Сообщение слишком длинное...") - break - self.write(peer_id, message[:4095 * i]) - else: - self.vk.messages.send(message=message, peer_id=peer_id, random_id=0) - - def rcon_cmd_handle(self, cmd, from_id, peer_id, _write=True, _allow=False): - a, r = self.perms.is_allowed(from_id, cmd.split()[0]) - if _allow: - r = cmd - if a or _allow: - answer = rcon(cmd) - logger.info(f"[BOT] User: {from_id}({r}) in Chat: {peer_id} use RCON cmd: \"{cmd}\", " - f"with answer: \"{answer}\"") - if _write: - self.write(peer_id, f"RCON:\n{answer}") - else: - return answer - else: - logger.info(f"[BOT] User: {from_id}({r}) in Chat: {peer_id} no have rights RCON cmd: \"{cmd}\".") - - def _bot_handle(self, message): - from_id = message['from_id'] - if self.perms.is_allowed(from_id, "bot"): - peer_id = message['peer_id'] - text = message['text'] - logger.info(f"[BOT] {peer_id}:{from_id}:{text}") - tsplit = text.split(" ") - cmds = ("Доступные команды:\n" - " .bot help - Вывести это сообщение.\n" - " .bot info - Выводит краткую информацию о боте.\n" - " .bot perm user <> - Добавить пользователя \n" - " .bot perm reload - Перезагружает пермишины") - if len(tsplit) == 1: - self.write(peer_id, cmds) - return - match tsplit[1]: - case "perm": - match tsplit[2] if len(tsplit) > 2 else None: - case "reload": - self.perms = Permissions.load() - self.write(peer_id, "Права перезагружены") - case _: - self.write(peer_id, ".bot perm ?") - case "info": - st = get_server_status() - self.write(peer_id, f"RconVkBot\n" - f"Версия бота: {modules.__version__}, последняя: {not is_new_version}\n" - f"Пинг до сервера: {st.latency:.3f}ms\n" - f"Бедрок: {st.motd.bedrock} ({st.version.protocol})\n") - case _: - self.write(peer_id, cmds) - - def message_handle(self, message): - from_id = message['from_id'] - peer_id = message['peer_id'] - text = message['text'] - match text: - case i if i.startswith(".rcon "): - self.rcon_cmd_handle(i[6:], from_id, peer_id) - case i if i.startswith(".bot"): - self._bot_handle(message) - case "!help": - logger.info(f"[BOT] {peer_id}:{from_id}:{text}") - self.write(peer_id, self.help_message) - case "!online": - logger.info(f"[BOT] {peer_id}:{from_id}:{text}") - players = get_server_status().players - self.write(peer_id, f"На сервере сейчас {players.online}/{players.max}") - case "!id": - logger.info(f"[BOT] {peer_id}:{from_id}:{text}") - self.write(peer_id, - f"Твой ID: {from_id}\n" - f"Роль: {self.perms.get_role(from_id)}\n" - f"Ник: {self.perms.get_nick(from_id)}") - - def listen(self): - server, key, ts = self.get_lp_server() - logger.info("[BOT] Начинаю получать сообщения..") - while True: - lp = requests.get(f'{server}?act=a_check&key={key}&ts={ts}&wait=25').json() - try: - if lp.get('failed') is not None: - key = self.get_lp_server()[1] - if ts != lp.get('ts') and lp.get('updates'): - updates = lp['updates'][0] - if updates['type'] == "message_new": - self.message_handle(updates['object']['message']) - ts = lp.get('ts') - except Exception as i: - ts = lp.get('ts') - logger.exception(i) diff --git a/src/modules/perms.py b/src/modules/perms.py index 47241ad..2232aa1 100644 --- a/src/modules/perms.py +++ b/src/modules/perms.py @@ -1,10 +1,9 @@ import os.path -import sys from pathlib import Path from loguru import logger -from modules import yaml +from modules import yaml, raw_config_perms class Permissions: @@ -15,14 +14,10 @@ class Permissions: self._no_nick = kwargs.get("noNick") self.no_rights = kwargs.get("noRights") self._perms = kwargs['perms'] + self._nicks = kwargs['nicks'] self._members = {} - if kwargs['useLuckPerms']: - logger.info("[PERMS] Поддержка LuckPerms всё ещё в разработке. ") - print(kwargs['LuckPerms']['nicks']) - sys.exit(1) - self._luck_perms = kwargs['LuckPerms'] - logger.info(f"[PERMS] {self.perm_file} - загружен") self.__handle_members() + logger.info(f"[PERMS] Права загружены") def __handle_members(self): for role, role_data in self._perms.items(): @@ -37,7 +32,7 @@ class Permissions: self._members[member] = { "role": role, "friendly": role_data.get("name", role), - "nick": self._luck_perms['nicks'].get(member) or self._no_nick, + "nick": self._nicks.get(member) or self._no_nick, "allow": allow } @@ -71,59 +66,12 @@ class Permissions: os.remove(cls.perm_file) return Permissions.load() else: - logger.info(f"Generating permissions file: {cls.perm_file}") - import textwrap - raw = textwrap.dedent("""\ - noRole: Нет роли - noRights: Нет прав # null для отключения - noNick: Не указан # Используется для !id, ник берётся из LuckPerms.nicks независимо от useLuckPerms - perms: - admins: # Имя группы - name: Админ # Имя группы, которое будет отображаться в боте - ids: # вк ИД входящих в состав группы - - 370926160 - allow: # Какие команды разрешены, "*" - все - - '*' - # Пример настройки - helpers: - name: Хелпер - ids: - - 583018016 - allow: - - say - - mute - - warn - - # Находится в режиме тестирования - # Интеграция с базой данных LuckPerms (Нужна именно внешняя база данных) - useLuckPerms: false - LuckPerms: - - # Таблица соответствия vkID к нику в Майнкрафте - nicks: - 370926160: Rick - 583018016: SantaSpeen - - # Разрешенные варианты: MySQL, MariaDB, PostgreSQL - storage-method: PostgreSQL - data: - # Указывайте host:port - address: 127.0.0.1:5432 - # База данных в которой хранятся настройки LuckPerms - database: minecraftDB - # Логин и пароль для доступа к БД - username: user - password: user - - # Смотрите настройку LuckPerms - table-prefix: luckperms_ - server: global - - """) - data = yaml.load(raw) + logger.info(f"Создание: {cls.perm_file}...") + data = yaml.load(raw_config_perms) with open(cls.perm_file, mode="w", encoding="utf-8") as f: yaml.dump(data, f) + logger.info(f"[PERMS] {cls.perm_file} - загружен") return Permissions(**data) diff --git a/src/modules/plugins_loader.py b/src/modules/plugins_loader.py new file mode 100644 index 0000000..e69de29