Compare commits

...

7 Commits

Author SHA1 Message Date
f2aba85982 [~] ... 2024-01-13 12:31:05 +03:00
a5b052e885 [~] Critical bugfix 2024-01-13 12:28:39 +03:00
9325621963 [~] ping in server check
[~] Bot to modules/bot.py
[~] 1.3.0 -> 1.3.1
2024-01-13 12:14:12 +03:00
dfccc91224 [+] Version checker
[+] cmd .bot info
[+] [perms] get_nick
2024-01-13 11:52:57 +03:00
69ea9343da [~] Update 2024-01-13 11:51:31 +03:00
2ef8d57836 [~] Update 2024-01-13 09:35:52 +03:00
d7135db073 [~] encoding
[!] Critical bugfix
[+] cmd `.bot`
[+] cmd `.bot perm reload`
[+] LuckPerm.nick
2024-01-13 09:35:44 +03:00
6 changed files with 209 additions and 121 deletions

View File

@ -10,6 +10,7 @@
* Доступные разрешённым людям * Доступные разрешённым людям
* **`.rcon <command>`** - Исполняет <*command*> и показывает ответ сервера * **`.rcon <command>`** - Исполняет <*command*> и показывает ответ сервера
* **`.bot`** - Команды бота, требует разрешения `bot`
* Доступные всем * Доступные всем
* **`!help`** - Выводит страничку с командами (Текст в файле help_message.txt) * **`!help`** - Выводит страничку с командами (Текст в файле help_message.txt)
* **`!online`** - Запрашивает у сервера онлайн и выводит * **`!online`** - Запрашивает у сервера онлайн и выводит
@ -41,11 +42,12 @@ _Всё очень легко и просто)_
## Система permissions ## Система permissions
В файле `permissions.yml` указаны все пользователи с "повышенным" уровнем доступа к боту В файле `permissions.yml` указаны все пользователи с "повышенным" уровнем доступа к боту\
Пример Пример
```yaml ```yaml
noRole: Нет роли noRole: Нет роли
noRights: Нет прав # null для отключения noRights: Нет прав # null для отключения
noNick: Не указан # Используется для !id, ник берётся из LuckPerms.nicks независимо от useLuckPerms
perms: perms:
admins: # Имя группы admins: # Имя группы
name: Админ # Имя группы, которое будет отображаться в боте name: Админ # Имя группы, которое будет отображаться в боте
@ -67,6 +69,12 @@ perms:
# Интеграция с базой данных LuckPerms (Нужна именно внешняя база данных) # Интеграция с базой данных LuckPerms (Нужна именно внешняя база данных)
useLuckPerms: false useLuckPerms: false
LuckPerms: LuckPerms:
# Таблица соответствия vkID к нику в Майнкрафте
nicks:
370926160: Rick
583018016: SantaSpeen
# Разрешенные варианты: MySQL, MariaDB, PostgreSQL # Разрешенные варианты: MySQL, MariaDB, PostgreSQL
storage-method: PostgreSQL storage-method: PostgreSQL
data: data:
@ -79,11 +87,11 @@ LuckPerms:
password: user password: user
# Смотрите настройку LuckPerms # Смотрите настройку LuckPerms
server: global
table-prefix: luckperms_ table-prefix: luckperms_
server: global
``` ```
LuckPerms **Интеграция с LuckPerms ещё не готова!**
### За помощью, заказами и предложениями можно обратиться сюда: ### За помощью, заказами и предложениями можно обратиться сюда:

View File

@ -1,112 +1,40 @@
import sys import sys
import traceback from pathlib import Path
import requests
import vk
from loguru import logger from loguru import logger
from modules import config, rcon, perms, get_server_status, enter_to_exit from modules import config, rcon, get_server_status, enter_to_exit
from modules.bot import Bot
from modules.perms import Permissions
class Bot:
def __init__(self):
self.vk = vk.API(access_token=config.vk.token, v=5.199)
self.group_id = self.vk.groups.getById()['groups'][0]['id']
with open('help_message.txt') 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.info("[BOT] Сообщение слишком длинное...")
break
self.write(peer_id, message[:4095 * i])
else:
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 = 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 message_handle(self, message):
from_id = message['from_id']
peer_id = message['peer_id']
text = message['text']
match text:
case i if i.startwith(".rcon "):
self.rcon_cmd_handle(i[6:], from_id, peer_id)
case "!help":
self.write(peer_id, self.help_message)
case "!online":
online = get_server_status().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()
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 KeyboardInterrupt:
raise KeyboardInterrupt
except Exception as i:
ts = lp.get('ts')
logger.exception(i)
if __name__ == '__main__': if __name__ == '__main__':
Permissions.perm_file = Path(config.permissions_file)
perms = Permissions.load()
# Check token
if not config.vk.token: if not config.vk.token:
logger.error("Токен ВК не найден.") logger.error("Токен ВК не найден.")
enter_to_exit() 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()
try: try:
bot = Bot()
# Test RCON
if rcon("list").startswith("Rcon error"):
logger.error("RCON не отвечает. Проверьте блок \"rcon\" в config.json")
enter_to_exit()
logger.info("RCON доступен.")
try:
# Test Minecraft Server
players = get_server_status().players
logger.info(f"Проверка сервера. Онлайн: {players.online}/{players.max}")
except Exception as e:
logger.exception(e)
logger.info("Сервер не отвечает. Проверьте блок \"minecraft\" в config.json")
enter_to_exit()
bot.listen() bot.listen()
except KeyboardInterrupt: except KeyboardInterrupt:
pass sys.exit(0)
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
enter_to_exit() enter_to_exit()

View File

@ -3,16 +3,20 @@ import json
import os import os
import re import re
import sys import sys
import traceback
import zipfile 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
import requests
from loguru import logger from loguru import logger
from mcrcon import MCRcon from mcrcon import MCRcon
from ruamel.yaml import YAML
from .perms import Permissions yaml = YAML()
yaml.default_flow_style = False
__version__ = '1.3.1'
raw_config = """\ raw_config = """\
{ {
@ -41,7 +45,7 @@ raw_help = """\
""" """
def zip_logs(): def init_logger():
log_file = "./logs/latest.log" log_file = "./logs/latest.log"
log_dir = os.path.dirname(log_file) + "/" log_dir = os.path.dirname(log_file) + "/"
if not os.path.exists(log_dir): if not os.path.exists(log_dir):
@ -62,11 +66,11 @@ def zip_logs():
os.remove(file) os.remove(file)
logger.remove(0) logger.remove(0)
logger.add(log_file) logger.add(log_file)
logger.add(sys.stdout, format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | " logger.add(sys.stdout, format="\r<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
"<level>{level: <8}</level> | {message}") "<level>{level: <8}</level> | {message}")
zip_logs() init_logger()
if not os.path.exists("config.json"): if not os.path.exists("config.json"):
logger.info("Создание: config.json...") logger.info("Создание: config.json...")
with open("config.json", "w") as f: with open("config.json", "w") as f:
@ -107,11 +111,25 @@ def get_server_status():
return server.status() return server.status()
Permissions.perm_file = Path(config.permissions_file) def enter_to_exit(exit_code=1):
perms = Permissions.load()
def enter_to_exit():
logger.info("Выход..") logger.info("Выход..")
input("\nНажмите Enter для продолжения..") input("\nНажмите Enter для продолжения..")
sys.exit(1) sys.exit(exit_code)
def new_version():
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 and ver != __version__:
logger.info("Обнаружена новая версия: {} -> {}", __version__, ver)
return True
except:
logger.error("Не получилось проверить обновления.")
else:
logger.info("У вас актуальная версия")
return False
is_new_version = new_version()

121
src/modules/bot.py Normal file
View File

@ -0,0 +1,121 @@
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)

View File

@ -3,10 +3,8 @@ import sys
from pathlib import Path from pathlib import Path
from loguru import logger from loguru import logger
from ruamel.yaml import YAML
yaml = YAML() from modules import yaml
yaml.default_flow_style = False
class Permissions: class Permissions:
@ -14,12 +12,13 @@ class Permissions:
def __init__(self, **kwargs): def __init__(self, **kwargs):
self._no_role = kwargs.get("noRole") self._no_role = kwargs.get("noRole")
self._no_nick = kwargs.get("noNick")
self.no_rights = kwargs.get("noRights") self.no_rights = kwargs.get("noRights")
self._perms = kwargs['perms'] self._perms = kwargs['perms']
self._members = {} self._members = {}
if kwargs['useLuckPerms']: if kwargs['useLuckPerms']:
logger.info("[PERMS] Using LuckPerms mode") logger.info("[PERMS] Поддержка LuckPerms всё ещё в разработке. ")
logger.info("[PERMS] LuckPerms mode support still in development") print(kwargs['LuckPerms']['nicks'])
sys.exit(1) sys.exit(1)
self._luck_perms = kwargs['LuckPerms'] self._luck_perms = kwargs['LuckPerms']
logger.info(f"[PERMS] {self.perm_file} - загружен") logger.info(f"[PERMS] {self.perm_file} - загружен")
@ -38,6 +37,7 @@ class Permissions:
self._members[member] = { self._members[member] = {
"role": role, "role": role,
"friendly": role_data.get("name", role), "friendly": role_data.get("name", role),
"nick": self._luck_perms['nicks'].get(member) or self._no_nick,
"allow": allow "allow": allow
} }
@ -57,6 +57,12 @@ class Permissions:
return u['friendly'] return u['friendly']
return self._no_role return self._no_role
def get_nick(self, member):
u = self._members.get(member)
if u:
return u.get("nick")
return self._no_nick
@classmethod @classmethod
def load(cls): def load(cls):
if os.path.exists(cls.perm_file): if os.path.exists(cls.perm_file):
@ -70,6 +76,7 @@ class Permissions:
raw = textwrap.dedent("""\ raw = textwrap.dedent("""\
noRole: Нет роли noRole: Нет роли
noRights: Нет прав # null для отключения noRights: Нет прав # null для отключения
noNick: Не указан # Используется для !id, ник берётся из LuckPerms.nicks независимо от useLuckPerms
perms: perms:
admins: # Имя группы admins: # Имя группы
name: Админ # Имя группы, которое будет отображаться в боте name: Админ # Имя группы, которое будет отображаться в боте
@ -92,6 +99,11 @@ class Permissions:
useLuckPerms: false useLuckPerms: false
LuckPerms: LuckPerms:
# Таблица соответствия vkID к нику в Майнкрафте
nicks:
370926160: Rick
583018016: SantaSpeen
# Разрешенные варианты: MySQL, MariaDB, PostgreSQL # Разрешенные варианты: MySQL, MariaDB, PostgreSQL
storage-method: PostgreSQL storage-method: PostgreSQL
data: data:
@ -104,8 +116,9 @@ class Permissions:
password: user password: user
# Смотрите настройку LuckPerms # Смотрите настройку LuckPerms
server: global
table-prefix: luckperms_ table-prefix: luckperms_
server: global
""") """)
data = yaml.load(raw) data = yaml.load(raw)
with open(cls.perm_file, mode="w", encoding="utf-8") as f: with open(cls.perm_file, mode="w", encoding="utf-8") as f:

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.3.0 Version: 1.3.1
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