diff --git a/.gitignore b/.gitignore index bbc333f..9b3fc0c 100644 --- a/.gitignore +++ b/.gitignore @@ -135,3 +135,4 @@ help_message.txt count* version.txt *.exe +logs/ diff --git a/README.md b/README.md index 0ade451..3950207 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ _Всё очень легко и просто)_ ## Система permissions -В файле permissions.yam указаны все пользователи с "повышенным" уровнем доступа к боту +В файле `permissions.yml` указаны все пользователи с "повышенным" уровнем доступа к боту Пример ```yaml noRole: Нет роли @@ -67,8 +67,6 @@ perms: # Интеграция с базой данных LuckPerms (Нужна именно внешняя база данных) useLuckPerms: false LuckPerms: - # Смотрите настройку LuckPerms - server: global # Разрешенные варианты: MySQL, MariaDB, PostgreSQL storage-method: PostgreSQL data: @@ -81,6 +79,7 @@ LuckPerms: password: user # Смотрите настройку LuckPerms + server: global table-prefix: luckperms_ ``` diff --git a/requirements.txt b/requirements.txt index 9181b8f..5b902a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ mcstatus~=11.1.1 mcrcon~=0.7.0 vk~=3.0 ruamel.yaml~=0.18.5 -requests~=2.31.0 \ No newline at end of file +requests~=2.31.0 +loguru~=0.7.2 \ No newline at end of file diff --git a/src/main.py b/src/main.py index 16bc44d..e097fcb 100644 --- a/src/main.py +++ b/src/main.py @@ -3,21 +3,22 @@ import traceback import requests import vk +from loguru import logger -from modules import log, config, rcon, perms, get_server_status +from modules import config, rcon, perms, get_server_status, enter_to_exit class Bot: def __init__(self): self.vk = vk.API(access_token=config.vk.token, v=5.199) - self.group_id = vk.groups.getById()[0]['id'] + self.group_id = self.vk.groups.getById()['groups'][0]['id'] with open('help_message.txt') as f: self.help_message = f.read() - log(f"[BOT] ID группы: {self.group_id}") + logger.info(f"[BOT] ID группы: {self.group_id}") def get_lp_server(self): - lp = vk.groups.getLongPollServer(group_id=self.group_id) + 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): @@ -25,7 +26,7 @@ class Bot: messages = (len(message) // 4095) for i in range(1, messages + 1): if i > 30: - log("[BOT] Сообщение слишком длинное...", 1) + logger.info("[BOT] Сообщение слишком длинное...") break self.write(peer_id, message[:4095 * i]) else: @@ -37,13 +38,14 @@ class Bot: r = cmd if a or _allow: answer = rcon(cmd) - log(f"[BOT] User: {from_id}({r}) in Chat: {peer_id} use RCON cmd: \"{cmd}\", with answer: \"{answer}\"") + 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: - log(f"[BOT] User: {from_id}({r}) in Chat: {peer_id} no have rights RCON cmd: \"{cmd}\".") + logger.info(f"[BOT] User: {from_id}({r}) in Chat: {peer_id} no have rights RCON cmd: \"{cmd}\".") def message_handle(self, message): from_id = message['from_id'] @@ -56,13 +58,13 @@ class Bot: self.write(peer_id, self.help_message) case "!online": online = get_server_status().online - self.write(peer_id, f"На сервере сейчас {online} {""}") + self.write(peer_id, f"На сервере сейчас {online}") case "!id": self.write(peer_id, f"Твой ID: {from_id}\nРоль: {perms.get_role(from_id)}") def listen(self): server, key, ts = self.get_lp_server() - log("[BOT] Начинаю получать сообщения..") + logger.info("[BOT] Начинаю получать сообщения..") while True: lp = requests.get(f'{server}?act=a_check&key={key}&ts={ts}&wait=25').json() try: @@ -75,41 +77,36 @@ class Bot: ts = lp.get('ts') - except KeyboardInterrupt as e: - raise e + except KeyboardInterrupt: + raise KeyboardInterrupt - except Exception as e: + except Exception as i: ts = lp.get('ts') - log(f"Found exception: {e}", 1) - traceback.print_exc() + logger.exception(i) if __name__ == '__main__': if not config.vk.token: - log("Токен ВК не найден.\nВыход...", 1) - input("\n\nНажмите Enter для продолжения..") - sys.exit(1) + logger.error("Токен ВК не найден.") + enter_to_exit() try: bot = Bot() - try: - # Test RCON - bot.rcon_cmd_handle("list", 0, 0, False, True) - log("RCON работает.") - except Exception as e: - log("RCON не отвечает. Проверьте блок \"rcon\" с config.json", 1) - raise e + # Test RCON + if rcon("list").startswith("Rcon error"): + logger.error("RCON не отвечает. Проверьте блок \"rcon\" в config.json") + enter_to_exit() + logger.info("RCON доступен.") try: # Test Minecraft Server - log(f"Проверка сервера. Онлайн: {get_server_status().online}") + players = get_server_status().players + logger.info(f"Проверка сервера. Онлайн: {players.online}/{players.max}") except Exception as e: - log("Сервер не отвечает. Проверьте блок \"minecraft\" с config.json", 1) - raise e + logger.exception(e) + logger.info("Сервер не отвечает. Проверьте блок \"minecraft\" в config.json") + enter_to_exit() bot.listen() except KeyboardInterrupt: pass except Exception as e: - log(f"Exception: {e}", 1) - traceback.print_exc() - finally: - log("Выход..") - input("\n\nНажмите Enter для продолжения..") + logger.exception(e) + enter_to_exit() diff --git a/src/modules/__init__.py b/src/modules/__init__.py index 45eee58..dbcb69c 100644 --- a/src/modules/__init__.py +++ b/src/modules/__init__.py @@ -1,20 +1,19 @@ +import glob import json import os import re +import sys import traceback +import zipfile from collections import namedtuple from datetime import datetime from pathlib import Path +from loguru import logger from mcrcon import MCRcon from .perms import Permissions - -def log(text, lvl=0): - print(f"[{datetime.now()}] [{['INFO ', 'ERROR'][lvl]}] {text}") - - raw_config = """\ { "vk": { @@ -41,24 +40,51 @@ raw_help = """\ Бот сделан кожанным петухом - админом, все вопросы к нему, я не причём. """ + +def zip_logs(): + log_file = "./logs/latest.log" + log_dir = os.path.dirname(log_file) + "/" + if not os.path.exists(log_dir): + os.makedirs(log_dir) + if os.path.exists(log_file): + ftime = os.path.getmtime(log_file) + zip_path = log_dir + datetime.fromtimestamp(ftime).strftime('%Y-%m-%d') + "-%s.zip" + index = 1 + while True: + if not os.path.exists(zip_path % index): + break + index += 1 + with zipfile.ZipFile(zip_path % index, "w") as zipf: + logs_files = glob.glob(f"{log_dir}/*.log") + for file in logs_files: + if os.path.exists(file): + zipf.write(file, os.path.basename(file)) + os.remove(file) + logger.remove(0) + logger.add(log_file) + logger.add(sys.stdout, format="{time:YYYY-MM-DD HH:mm:ss.SSS} | " + "{level: <8} | {message}") + + +zip_logs() if not os.path.exists("config.json"): - log("Создание: config.json...") + logger.info("Создание: config.json...") with open("config.json", "w") as f: f.write(raw_config) with open('config.json') as f: config = json.load(f, object_hook=lambda x: namedtuple('X', x.keys())(*x.values())) -log("Запуск..") +logger.info("Запуск..") if not os.path.exists(config.vk.help_file): - log(f"Создание: {config.vk.help_file}...") + logger.info(f"Создание: {config.vk.help_file}...") with open(config.vk.help_file, "w") as f: f.write(raw_help) if config.minecraft.java: - from mcstatus import JavaServer as mcs + from mcstatus import JavaServer as MineServer else: - from mcstatus import BedrockServer as mcs + from mcstatus import BedrockServer as MineServer host = config.rcon.host port = config.rcon.port @@ -71,15 +97,21 @@ def rcon(cmd): text = mcr.command(cmd) return re.sub(r'§.', '', text) except Exception as e: - log(f"[RCON] ERROR with command: {cmd}", 1) - print(traceback.format_exc()) + logger.error(f"[RCON] ERROR with command: {cmd}") + logger.exception(e) return f"Rcon error: {e}" def get_server_status(): - server = mcs.lookup(config.minecraft.host, config.minecraft.port) + server = MineServer.lookup(config.minecraft.host, config.minecraft.port) return server.status() Permissions.perm_file = Path(config.permissions_file) perms = Permissions.load() + + +def enter_to_exit(): + logger.info("Выход..") + input("\nНажмите Enter для продолжения..") + sys.exit(1) diff --git a/win/metadata.yml b/win/metadata.yml index 87df74c..3eda1d2 100644 --- a/win/metadata.yml +++ b/win/metadata.yml @@ -1,6 +1,6 @@ # pip install pyinstaller-versionfile # create-version-file metadata.yml --outfile version.txt -Version: 1.2.3 +Version: 1.3.0 CompanyName: anidev FileDescription: Бот для майнкрафта, использует RCON и VK API. Исходники можно найти по "SantaSpeen/Rcon-VK-Bot" InternalName: VkBot-Rcon