[-] plugins (?)

[+] Optimize project
[+] config_dir (?)
This commit is contained in:
Maxim Khomutov 2024-01-24 17:32:45 +03:00
parent 2710cb6e14
commit c12cdb1630
14 changed files with 326 additions and 367 deletions

View File

@ -4,4 +4,5 @@ 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 loguru~=0.7.2
easydict~=1.11 easydict~=1.11
SQLAlchemy~=2.0.25

View File

@ -1 +1,256 @@
from .bot import Bot import glob
import os
import platform
import signal
import sys
import zipfile
from datetime import datetime
import requests
from loguru import logger
from ruamel.yaml import YAML
yaml = YAML()
yaml.default_flow_style = False
IN_DOCKER = "IN_DOCKER" in os.environ
__version__ = '2.1.0'
config_dir = "config/" if IN_DOCKER else ""
raw_config_main = f"""\
# Работа протестирована на LongPool 5.199
vk_token: "" # Токен ВК
help_file: {config_dir}help_message.txt # Файл, который будет выводиться на !help
perms_file: {config_dir}permissions.yml # Конфиг с настройками разрешений для пользователей
hosts_file: {config_dir}hosts.yml # Конфиг с настройками хостов
store_file: {config_dir}store/sql.yml # Конфиг с настройками где хранить историю онлайна
"""
raw_config_perms = """\
noRole: Нет роли
noRights: Нет прав # null для отключения
noNick: Не указан # Используется для !id
# Таблица соответствия vkID к нику в Майнкрафте
# Ник будет передаваться в плагины (Плагины бота)
nicks:
370926160: Rick
583018016: SantaSpeen
perms:
admin: # Имя группы
name: Админ # Имя группы, которое будет отображаться в боте
ids: # вк ИД входящих в состав группы
- 370926160
parent: # Наследование прав
- helper
allow: # Права, подробнее в readme.md
- bot.*
# - bot.help
# - bot.info
# - bot.hosts
# - bot.hosts.*
# - bot.hosts.list
# - bot.hosts.reload
# - bot.perms
# - bot.hosts.*
# - bot.hosts.reload
# - bot.cmd.*
# - bot.cmd.help
# - bot.cmd.id
# - bot.online.*
# - bot.online.default
# - bot.online.lobby
# - bot.history.*
# - bot.history.default
# - bot.history.lobby
# - bot.rcon.*
# - bot.rcon.*.*
helper:
name: Хелпер
ids:
- 583018016
allow:
- bot.rcon.default
- bot.rcon.lobby
- bot.rcon.survival
- bot.rcon.*.say
- bot.rcon.*.mute
- bot.rcon.survival.ban
- bot.rcon.survival.tempban
default:
name: Игрок
allow:
- bot.cmd.help
- bot.cmd.id
- bot.cmd.online.*
- bot.cmd.history.*
"""
raw_config_hosts = """\
hosts:
survival: # Название сервера (имя), может быть любым, может быть сколько угодно
meta:
name: Выживание # Это имя будет выводиться в ответе, или статистике (имя для бота)
java: true # Это JAVA сервер?
important: true # Если да, то бот не включится, если не подключится
# 0 - выключен
# 1 - Доступно без имени (!! Такое значение может быть только у 1 хоста !!) (.rcon <cmd>)
# 2 - Доступно с именем (.rcon <name> <cmd>)
# Разрешение: bot.rcon.<name>; bot.online.<name>; bot.history.<name>
# При запуске бота будет проверка доступности всего
rcon: 2 # RCON будет доступен по команде .rcon survival <cmd> (разрешение: bot.rcon.survival)
# !online будет доступен по команде !online survival (разрешение: bot.cmd.online.survival)
# !history будет доступен по команде !history survival (разрешение: bot.cmd.history.survival)
online: 2
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
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
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
rcon:
mine:
host: 192.168.0.31
port: 15009
"""
raw_help = """\
!help - Вывести это сообщение
!online - Показать текущий онлайн на сервере
"""
config_file_main = config_dir + "bot.yml"
if config_dir and not os.path.exists(config_dir):
os.makedirs(config_dir)
def init_logger():
log_debug = "./logs/debug.log"
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_debug, level=0, backtrace=True, diagnose=True)
logger.add(log_file, level="INFO", backtrace=False, diagnose=False,
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {message}")
logger.add(sys.stdout, level="INFO", backtrace=False, diagnose=False,
format="\r<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | {message}")
init_logger()
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", encoding="utf-8") as f:
yaml.dump(c, f)
with open(config_file_main, encoding="utf-8") as f:
config = yaml.load(f)
logger.info("Запуск..")
if IN_DOCKER:
logger.info("Обнаружен запуск из DOCKER")
if not os.path.exists(config["help_file"]):
logger.info(f"Создание: {config["help_file"]}...")
with open(config["help_file"], "w", encoding="utf-8") as f:
f.write(raw_help)
def enter_to_exit(exit_code=1):
logger.info("Выход..")
if not IN_DOCKER:
input("\nНажмите Enter для продолжения..")
sys.exit(exit_code)
def new_version():
print("Проверка версии...", end="")
try:
res = requests.get("https://raw.githubusercontent.com/SantaSpeen/Rcon-VK-Bot/master/win/metadata.yml")
data = yaml.load(res.text)
ver = data.get("Version")
if ver:
if int(ver.replace(".", "").replace("-", "")) > int(__version__.replace(".", "").replace("-", "")):
logger.info("Обнаружена новая версия: {} -> {}", __version__, ver)
return True
else:
logger.warning(f"У вас DEV версия: {__version__} (Актуальная: {ver})")
return "DEV"
except Exception as e:
logger.error(f"Не получилось проверить обновления: {e}")
else:
logger.info("У вас актуальная версия")
return False
is_new_version = new_version()
def get_bot():
from .bot import Bot
bot = Bot()
signal.signal(signal.SIGTERM, bot.stop)
signal.signal(signal.SIGINT, bot.stop)
if platform.system() == 'Windows':
signal.signal(signal.SIGBREAK, bot.stop)
elif not IN_DOCKER:
# signal.signal(signal.SIGKILL, bot.stop)
signal.signal(signal.SIGHUP, bot.stop)
return bot

View File

@ -6,9 +6,8 @@ import vk
from easydict import EasyDict from easydict import EasyDict
from loguru import logger from loguru import logger
import modules from . import config, is_new_version, enter_to_exit, __version__
from modules import config, is_new_version from .perms import Permissions
from modules.perms import Permissions
from .hosts import Hosts from .hosts import Hosts
@ -28,11 +27,12 @@ class Bot:
def _test(self): def _test(self):
Permissions.perms_file = Path(config["perms_file"]) Permissions.perms_file = Path(config["perms_file"])
self.perms = Permissions.load() self.perms = Permissions.load()
Hosts.hosts_file = Path(config["hosts_file"])
self.hosts = Hosts.load() self.hosts = Hosts.load()
# Check token # Check token
if not config["vk_token"]: if not config["vk_token"]:
logger.error("Токен ВК не найден.") logger.error("Токен ВК не найден.")
modules.enter_to_exit() enter_to_exit()
def get_lp_server(self): def get_lp_server(self):
lp = self.vk.groups.getLongPollServer(group_id=self.group_id) lp = self.vk.groups.getLongPollServer(group_id=self.group_id)
@ -113,7 +113,7 @@ class Bot:
case "info": case "info":
if not message.has_perm(["bot.info"]): return if not message.has_perm(["bot.info"]): return
message.reply(f"RconVkBot\n" message.reply(f"RconVkBot\n"
f"Версия бота: {modules.__version__}, последняя: {not is_new_version}") f"Версия бота: {__version__}, последняя: {not is_new_version}")
case _: case _:
if not message.has_perm(["bot.help"]): return if not message.has_perm(["bot.help"]): return
message.reply(cmds) message.reply(cmds)

51
src/core/history.py Normal file
View File

@ -0,0 +1,51 @@
import time
from datetime import datetime
from threading import Thread
from loguru import logger
from sqlalchemy import create_engine, Column, Integer, DateTime
from sqlalchemy.orm import sessionmaker, declarative_base
Base = declarative_base()
class OnlineStats(Base):
__tablename__ = 'online_stats'
id = Column(Integer, primary_key=True)
timestamp = Column(DateTime, default=datetime.now)
users_online = Column(Integer)
class History:
def __init__(self):
self.hosts = None
self.thread = None
self.stop = False
self.engine = create_engine('sqlite:///./stats.db', echo=False)
Base.metadata.create_all(self.engine)
self.session = sessionmaker(bind=self.engine)()
def _run(self):
while not self.stop:
time.sleep(1)
def load(self, hosts):
self.unload()
self.stop = False
self.hosts = hosts
self.thread = Thread(target=self._run)
self.thread.start()
logger.info("[HOSTS] Поток для отслеживания запущен")
return self
def unload(self, stop=False):
"""Останавливает поток"""
if self.thread:
self.stop = True
self.thread.join()
self.thread = None
if stop:
self.session.close()

View File

@ -6,11 +6,11 @@ from loguru import logger
from mcrcon import MCRcon from mcrcon import MCRcon
from mcstatus import JavaServer, BedrockServer from mcstatus import JavaServer, BedrockServer
from modules import yaml, raw_config_hosts, enter_to_exit from core import yaml, raw_config_hosts, enter_to_exit
class Hosts: class Hosts:
hosts_config = Path("config/hosts.yml") hosts_file = Path("config/hosts.yml")
def __init__(self, **kwargs): def __init__(self, **kwargs):
self._hosts = kwargs["hosts"] self._hosts = kwargs["hosts"]
@ -19,6 +19,8 @@ class Hosts:
self._hosts_mine = {} self._hosts_mine = {}
self._hosts_meta = {} self._hosts_meta = {}
self._connect() self._connect()
self.mine = self._hosts_mine
self.meta = self._hosts_meta
logger.info("[HOSTS] Хосты загружены") logger.info("[HOSTS] Хосты загружены")
def rcon(self, cmd: str, server: str = "default", update=False) -> tuple[str | None, Exception | None]: def rcon(self, cmd: str, server: str = "default", update=False) -> tuple[str | None, Exception | None]:
@ -122,17 +124,17 @@ class Hosts:
@classmethod @classmethod
def load(cls) -> "Hosts": def load(cls) -> "Hosts":
if os.path.exists(cls.hosts_config): if os.path.exists(cls.hosts_file):
data = yaml.load(cls.hosts_config) data = yaml.load(cls.hosts_file)
if not data: if not data:
os.remove(cls.hosts_config) os.remove(cls.hosts_file)
return cls.load() return cls.load()
else: else:
data = yaml.load(raw_config_hosts) data = yaml.load(raw_config_hosts)
with open(cls.hosts_config, mode="w", encoding="utf-8") as f: with open(cls.hosts_file, mode="w", encoding="utf-8") as f:
yaml.dump(data, f) yaml.dump(data, f)
logger.info(f"[HOSTS] {cls.hosts_config} - загружен") logger.info(f"[HOSTS] {cls.hosts_file} - загружен")
return Hosts(**data) return Hosts(**data)
def unload(self): def unload(self):

View File

@ -3,7 +3,7 @@ from pathlib import Path
from loguru import logger from loguru import logger
from modules import yaml, raw_config_perms, enter_to_exit from core import yaml, raw_config_perms, enter_to_exit
class Permissions: class Permissions:

View File

@ -1,20 +1,9 @@
import platform
import signal
from loguru import logger from loguru import logger
from core import Bot from core import get_bot, enter_to_exit
from modules import enter_to_exit, IN_DOCKER
if __name__ == '__main__': if __name__ == '__main__':
bot = Bot() bot = get_bot()
signal.signal(signal.SIGTERM, bot.stop)
signal.signal(signal.SIGINT, bot.stop)
if platform.system() == 'Windows':
signal.signal(signal.SIGBREAK, bot.stop)
elif not IN_DOCKER:
# signal.signal(signal.SIGKILL, bot.stop)
signal.signal(signal.SIGHUP, bot.stop)
try: try:
bot.listen() bot.listen()
except Exception as e: except Exception as e:

View File

@ -1,233 +0,0 @@
import glob
import os
import sys
import zipfile
from datetime import datetime
import requests
from loguru import logger
from ruamel.yaml import YAML
yaml = YAML()
yaml.default_flow_style = False
IN_DOCKER = "IN_DOCKER" in os.environ
__version__ = '2.0.0'
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: # Права, подробнее в readme.md
- bot.*
# - bot.help
# - bot.info
# - bot.hosts
# - bot.hosts.*
# - bot.hosts.list
# - bot.hosts.reload
# - bot.perms
# - bot.hosts.*
# - bot.hosts.reload
# - bot.cmd.*
# - bot.cmd.help
# - bot.cmd.id
# - bot.online.*
# - bot.online.default
# - bot.online.lobby
# - bot.history.*
# - bot.history.default
# - bot.history.lobby
# - bot.rcon.*
# - bot.rcon.*.*
helper:
name: Хелпер
ids:
- 583018016
allow:
- bot.rcon.default
- bot.rcon.lobby
- bot.rcon.survival
- bot.rcon.*.say
- bot.rcon.*.mute
- bot.rcon.survival.ban
- bot.rcon.survival.tempban
default:
name: Игрок
allow:
- bot.cmd.help
- bot.cmd.id
- bot.cmd.online.*
- bot.cmd.history.*
"""
raw_config_hosts = """\
hosts:
survival: # Название сервера (имя), может быть любым, может быть сколько угодно
meta:
name: Выживание # Это имя будет выводиться в ответе, или статистике (имя для бота)
java: true # Это JAVA сервер?
important: true # Если да, то бот не включится, если не подключится
# 0 - выключен
# 1 - Доступно без имени (!! Такое значение может быть только у 1 хоста !!) (.rcon <cmd>)
# 2 - Доступно с именем (.rcon <name> <cmd>)
# Разрешение: bot.rcon.<name>; bot.online.<name>; bot.history.<name>
# При запуске бота будет проверка доступности всего
rcon: 2 # RCON будет доступен по команде .rcon lobby <cmd> (разрешение: bot.rcon.lobby)
# !online будет доступен по команде !online lobby (разрешение: bot.cmd.online.lobby)
# !history будет доступен по команде !history lobby (разрешение: bot.cmd.history.lobby)
online: 2
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
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
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
rcon:
mine:
host: 192.168.0.31
port: 15009
"""
raw_help = """\
!help - Вывести это сообщение
!online - Показать текущий онлайн на сервере
"""
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_debug = "./logs/debug.log"
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_debug, level=0, backtrace=True, diagnose=True)
logger.add(log_file, level="INFO", backtrace=False, diagnose=False,
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {message}")
logger.add(sys.stdout, level="INFO", backtrace=False, diagnose=False,
format="\r<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | {message}")
init_logger()
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_file_main) as f:
config = yaml.load(f)
logger.info("Запуск..")
if IN_DOCKER:
logger.info("Обнаружен запуск из DOCKER")
if not os.path.exists(config["help_file"]):
logger.info(f"Создание: {config["help_file"]}...")
with open(config["help_file"], "w", encoding="utf-8") as f:
f.write(raw_help)
def enter_to_exit(exit_code=1):
logger.info("Выход..")
if not IN_DOCKER:
input("\nНажмите Enter для продолжения..")
sys.exit(exit_code)
def new_version():
print("Проверка версии...", end="")
try:
res = requests.get("https://raw.githubusercontent.com/SantaSpeen/Rcon-VK-Bot/master/win/metadata.yml", timeout=3)
data = yaml.load(res.text)
ver = data.get("Version")
if ver and ver != __version__:
logger.info("Обнаружена новая версия: {} -> {}", __version__, ver)
return True
except Exception as e:
logger.error(f"Не получилось проверить обновления: {e}")
else:
logger.info("У вас актуальная версия")
return False
is_new_version = new_version()

View File

@ -1,20 +0,0 @@
try:
import Bot
import logger
except ImportError:
Bot = object
class Plugin(Bot):
ac_dir = "./perms"
def __init__(self):
super(Plugin, self).__init__()
logger.info(f"Инициализация {self.name} v{self.version}")
async def load(self):
pass
async def unload(self):
pass

View File

@ -1,16 +0,0 @@
LuckPerms:
# Разрешенные варианты: 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

View File

@ -1,61 +0,0 @@
# /cmd cmd: perm - Можно только <perm>
# /cmd cmd: noOne - Команду нельзя использовать из под бота
enabled: false
pluginName: AuthMeReloaded 5.6.0-beta2
data:
/authme register: authme.admin.register
/authme unregister: authme.admin.unregister
/authme forcelogin: authme.admin.forcelogin
/authme password: authme.admin.changepassword
/authme lastlogin: authme.admin.lastlogin
/authme accounts: authme.admin.accounts
/authme email: authme.admin.getemail
/authme setemail: authme.admin.changemail
/authme getip: authme.admin.getip
/authme totp: authme.admin.totpviewstatus
/authme disabletotp: authme.admin.totpdisable
/authme spawn: authme.admin.spawn
/authme setspawn: authme.admin.setspawn
/authme firstspawn: authme.admin.firstspawn
/authme setfirstspawn: authme.admin.setfirstspawn
/authme purge: authme.admin.purge
/authme purgeplayer: authme.admin.purgeplayer
/authme backup: authme.admin.backup
/authme resetpos: authme.admin.purgelastpos
/authme purgebannedplayers: authme.admin.purgebannedplayers
/authme switchantibot: authme.admin.switchantibot
/authme reload: authme.admin.reload
/authme version: authme.admin # Вставил так как стандартно есть у всех
/authme converter: authme.admin.converter
/authme messages: authme.admin.updatemessages
/authme recent: authme.admin.seerecent
/authme debug: authme.debug.command
/authme help: noOne
/email: noOne
/email show: noOne
/email add: noOne
/email change: noOne
/email recover: noOne
/email code: noOne
/email setpassword: noOne
/email help: noOne
/login: noOne
/login help: noOne
/logout: noOne
/logout help: noOne
/register: noOne
/register help: noOne
/unregister: noOne
/unregister help: noOne
/changepassword: noOne
/changepassword help: noOne
/totp: noOne
/totp code: noOne
/totp add: noOne
/totp confirm: noOne
/totp remove: noOne
/totp help: noOne
/captcha: noOne
/captcha help: noOne
/verification: noOne
/verification help: noOne

View File

@ -1,5 +0,0 @@
enabled: false
version: 0.1
name: LuckPerms Integration
dependencies:
- bot >1.3 # Это сам бот

View File

@ -1,4 +0,0 @@
# Поддержка плагинов
Пока ещё в работе