Compare commits

...

4 Commits

Author SHA1 Message Date
a93f18fa47 [~] encoding 2024-01-12 21:10:25 +03:00
26c086ceb6 .idea 2024-01-12 21:09:00 +03:00
b20394ed6f [~] 1.2.3 -> 1.3.0
[!] Critical bugfix
[+] Logging
[+] Logging to file
[~] Minor
2024-01-12 21:08:46 +03:00
28d9f62667 .idea 2024-01-12 21:04:49 +03:00
9 changed files with 91 additions and 65 deletions

1
.gitignore vendored
View File

@ -135,3 +135,4 @@ help_message.txt
count* count*
version.txt version.txt
*.exe *.exe
logs/

2
.idea/Rcon-VK-Bot.iml generated
View File

@ -6,7 +6,7 @@
<excludeFolder url="file://$MODULE_DIR$/.venv" /> <excludeFolder url="file://$MODULE_DIR$/.venv" />
<excludeFolder url="file://$MODULE_DIR$/venv" /> <excludeFolder url="file://$MODULE_DIR$/venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.12 (Rcon-VK-Bot) (2)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.12 (Rcon-VK-Bot)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

2
.idea/misc.xml generated
View File

@ -3,5 +3,5 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="Python 3.10 (Rcon-VK-Bot)" /> <option name="sdkName" value="Python 3.10 (Rcon-VK-Bot)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (Rcon-VK-Bot) (2)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (Rcon-VK-Bot)" project-jdk-type="Python SDK" />
</project> </project>

View File

@ -41,7 +41,7 @@ _Всё очень легко и просто)_
## Система permissions ## Система permissions
В файле permissions.yam указаны все пользователи с "повышенным" уровнем доступа к боту В файле `permissions.yml` указаны все пользователи с "повышенным" уровнем доступа к боту
Пример Пример
```yaml ```yaml
noRole: Нет роли noRole: Нет роли
@ -67,8 +67,6 @@ perms:
# Интеграция с базой данных LuckPerms (Нужна именно внешняя база данных) # Интеграция с базой данных LuckPerms (Нужна именно внешняя база данных)
useLuckPerms: false useLuckPerms: false
LuckPerms: LuckPerms:
# Смотрите настройку LuckPerms
server: global
# Разрешенные варианты: MySQL, MariaDB, PostgreSQL # Разрешенные варианты: MySQL, MariaDB, PostgreSQL
storage-method: PostgreSQL storage-method: PostgreSQL
data: data:
@ -81,6 +79,7 @@ LuckPerms:
password: user password: user
# Смотрите настройку LuckPerms # Смотрите настройку LuckPerms
server: global
table-prefix: luckperms_ table-prefix: luckperms_
``` ```

View File

@ -3,3 +3,4 @@ mcrcon~=0.7.0
vk~=3.0 vk~=3.0
ruamel.yaml~=0.18.5 ruamel.yaml~=0.18.5
requests~=2.31.0 requests~=2.31.0
loguru~=0.7.2

View File

@ -3,21 +3,22 @@ import traceback
import requests import requests
import vk 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: class Bot:
def __init__(self): def __init__(self):
self.vk = vk.API(access_token=config.vk.token, v=5.199) 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: with open('help_message.txt') as f:
self.help_message = f.read() 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): 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') return lp.get('server'), lp.get('key'), lp.get('ts')
def write(self, peer_id, message): def write(self, peer_id, message):
@ -25,7 +26,7 @@ class Bot:
messages = (len(message) // 4095) messages = (len(message) // 4095)
for i in range(1, messages + 1): for i in range(1, messages + 1):
if i > 30: if i > 30:
log("[BOT] Сообщение слишком длинное...", 1) logger.info("[BOT] Сообщение слишком длинное...")
break break
self.write(peer_id, message[:4095 * i]) self.write(peer_id, message[:4095 * i])
else: else:
@ -37,13 +38,14 @@ class Bot:
r = cmd r = cmd
if a or _allow: if a or _allow:
answer = rcon(cmd) 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: if _write:
self.write(peer_id, f"RCON:\n{answer}") self.write(peer_id, f"RCON:\n{answer}")
else: else:
return answer return answer
else: 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): def message_handle(self, message):
from_id = message['from_id'] from_id = message['from_id']
@ -56,13 +58,13 @@ class Bot:
self.write(peer_id, self.help_message) self.write(peer_id, self.help_message)
case "!online": case "!online":
online = get_server_status().online online = get_server_status().online
self.write(peer_id, f"На сервере сейчас {online} {""}") self.write(peer_id, f"На сервере сейчас {online}")
case "!id": case "!id":
self.write(peer_id, f"Твой ID: {from_id}\nРоль: {perms.get_role(from_id)}") self.write(peer_id, f"Твой ID: {from_id}\nРоль: {perms.get_role(from_id)}")
def listen(self): def listen(self):
server, key, ts = self.get_lp_server() server, key, ts = self.get_lp_server()
log("[BOT] Начинаю получать сообщения..") logger.info("[BOT] Начинаю получать сообщения..")
while True: while True:
lp = requests.get(f'{server}?act=a_check&key={key}&ts={ts}&wait=25').json() lp = requests.get(f'{server}?act=a_check&key={key}&ts={ts}&wait=25').json()
try: try:
@ -75,41 +77,36 @@ class Bot:
ts = lp.get('ts') ts = lp.get('ts')
except KeyboardInterrupt as e: except KeyboardInterrupt:
raise e raise KeyboardInterrupt
except Exception as e: except Exception as i:
ts = lp.get('ts') ts = lp.get('ts')
log(f"Found exception: {e}", 1) logger.exception(i)
traceback.print_exc()
if __name__ == '__main__': if __name__ == '__main__':
if not config.vk.token: if not config.vk.token:
log("Токен ВК не найден.\nВыход...", 1) logger.error("Токен ВК не найден.")
input("\n\nНажмите Enter для продолжения..") enter_to_exit()
sys.exit(1)
try: try:
bot = Bot() bot = Bot()
try:
# Test RCON # Test RCON
bot.rcon_cmd_handle("list", 0, 0, False, True) if rcon("list").startswith("Rcon error"):
log("RCON работает.") logger.error("RCON не отвечает. Проверьте блок \"rcon\" в config.json")
except Exception as e: enter_to_exit()
log("RCON не отвечает. Проверьте блок \"rcon\" с config.json", 1) logger.info("RCON доступен.")
raise e
try: try:
# Test Minecraft Server # 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: except Exception as e:
log("Сервер не отвечает. Проверьте блок \"minecraft\" с config.json", 1) logger.exception(e)
raise e logger.info("Сервер не отвечает. Проверьте блок \"minecraft\" в config.json")
enter_to_exit()
bot.listen() bot.listen()
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
except Exception as e: except Exception as e:
log(f"Exception: {e}", 1) logger.exception(e)
traceback.print_exc() enter_to_exit()
finally:
log("Выход..")
input("\n\nНажмите Enter для продолжения..")

View File

@ -1,20 +1,19 @@
import glob
import json import json
import os import os
import re import re
import sys
import traceback import traceback
import zipfile
from collections import namedtuple from collections import namedtuple
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from loguru import logger
from mcrcon import MCRcon from mcrcon import MCRcon
from .perms import Permissions from .perms import Permissions
def log(text, lvl=0):
print(f"[{datetime.now()}] [{['INFO ', 'ERROR'][lvl]}] {text}")
raw_config = """\ raw_config = """\
{ {
"vk": { "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="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
"<level>{level: <8}</level> | {message}")
zip_logs()
if not os.path.exists("config.json"): if not os.path.exists("config.json"):
log("Создание: config.json...") logger.info("Создание: config.json...")
with open("config.json", "w") as f: with open("config.json", "w") as f:
f.write(raw_config) f.write(raw_config)
with open('config.json') as f: with open('config.json') as f:
config = json.load(f, object_hook=lambda x: namedtuple('X', x.keys())(*x.values())) 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): 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: with open(config.vk.help_file, "w", encoding="utf-8") as f:
f.write(raw_help) f.write(raw_help)
if config.minecraft.java: if config.minecraft.java:
from mcstatus import JavaServer as mcs from mcstatus import JavaServer as MineServer
else: else:
from mcstatus import BedrockServer as mcs from mcstatus import BedrockServer as MineServer
host = config.rcon.host host = config.rcon.host
port = config.rcon.port port = config.rcon.port
@ -71,15 +97,21 @@ def rcon(cmd):
text = mcr.command(cmd) text = mcr.command(cmd)
return re.sub(r'§.', '', text) return re.sub(r'§.', '', text)
except Exception as e: except Exception as e:
log(f"[RCON] ERROR with command: {cmd}", 1) logger.error(f"[RCON] ERROR with command: {cmd}")
print(traceback.format_exc()) logger.exception(e)
return f"Rcon error: {e}" return f"Rcon error: {e}"
def get_server_status(): 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() return server.status()
Permissions.perm_file = Path(config.permissions_file) Permissions.perm_file = Path(config.permissions_file)
perms = Permissions.load() perms = Permissions.load()
def enter_to_exit():
logger.info("Выход..")
input("\nНажмите Enter для продолжения..")
sys.exit(1)

View File

@ -1,14 +1,10 @@
import os.path import os.path
from datetime import datetime import sys
from pathlib import Path from pathlib import Path
from loguru import logger
from ruamel.yaml import YAML from ruamel.yaml import YAML
def log(text, lvl=0):
print(f"[{datetime.now()}] [{['INFO ', 'ERROR'][lvl]}] {text}")
yaml = YAML() yaml = YAML()
yaml.default_flow_style = False yaml.default_flow_style = False
@ -22,11 +18,11 @@ class Permissions:
self._perms = kwargs['perms'] self._perms = kwargs['perms']
self._members = {} self._members = {}
if kwargs['useLuckPerms']: if kwargs['useLuckPerms']:
log("[PERMS] Using LuckPerms mode") logger.info("[PERMS] Using LuckPerms mode")
log("[PERMS] LuckPerms mode support still in development") logger.info("[PERMS] LuckPerms mode support still in development")
sys.exit(1) sys.exit(1)
self._luck_perms = kwargs['LuckPerms'] self._luck_perms = kwargs['LuckPerms']
log("[PERMS] Permissions loaded") logger.info(f"[PERMS] {self.perm_file} - загружен")
self.__handle_members() self.__handle_members()
def __handle_members(self): def __handle_members(self):
@ -69,7 +65,7 @@ class Permissions:
os.remove(cls.perm_file) os.remove(cls.perm_file)
return Permissions.load() return Permissions.load()
else: else:
log(f"Generating permissions file: {cls.perm_file}") logger.info(f"Generating permissions file: {cls.perm_file}")
import textwrap import textwrap
raw = textwrap.dedent("""\ raw = textwrap.dedent("""\
noRole: Нет роли noRole: Нет роли
@ -95,8 +91,7 @@ class Permissions:
# Интеграция с базой данных LuckPerms (Нужна именно внешняя база данных) # Интеграция с базой данных LuckPerms (Нужна именно внешняя база данных)
useLuckPerms: false useLuckPerms: false
LuckPerms: LuckPerms:
# Смотрите настройку LuckPerms
server: global
# Разрешенные варианты: MySQL, MariaDB, PostgreSQL # Разрешенные варианты: MySQL, MariaDB, PostgreSQL
storage-method: PostgreSQL storage-method: PostgreSQL
data: data:
@ -109,6 +104,7 @@ class Permissions:
password: user password: user
# Смотрите настройку LuckPerms # Смотрите настройку LuckPerms
server: global
table-prefix: luckperms_ table-prefix: luckperms_
""") """)
data = yaml.load(raw) data = yaml.load(raw)

View File

@ -1,6 +1,6 @@
# pip install pyinstaller-versionfile # pip install pyinstaller-versionfile
# create-version-file metadata.yml --outfile version.txt # create-version-file metadata.yml --outfile version.txt
Version: 1.2.3 Version: 1.3.0
CompanyName: anidev CompanyName: anidev
FileDescription: Бот для майнкрафта, использует RCON и VK API. Исходники можно найти по "SantaSpeen/Rcon-VK-Bot" FileDescription: Бот для майнкрафта, использует RCON и VK API. Исходники можно найти по "SantaSpeen/Rcon-VK-Bot"
InternalName: VkBot-Rcon InternalName: VkBot-Rcon