Compare commits

...

5 Commits

Author SHA1 Message Date
9cae0e44b1 [-] signal.SIGKILL 2024-01-17 06:19:01 +03:00
a287f7ce11 [+] Update info 2024-01-17 06:00:14 +03:00
fc3279acfe [+] Permission System (into bot)
[+] MultiServer System
[+] (bot) perms_handler
[+] (hosts) new api
[+] IN_DOCKER
[+] Signals handlers
[+] easydict
[~] Version 1.3.0 -> 2.0.0
[~] (perms) Update is_allow
2024-01-17 05:59:28 +03:00
2237582f80 [+] Permission System (into bot)
[+] MultiServer System
[+] (bot) perms_handler
[+] (hosts) new api
[+] IN_DOCKER
[+] Signals handlers
[+] easydict
[~] Version 1.3.0 -> 2.0.0
[~] (perms) Update is_allow
2024-01-17 05:59:24 +03:00
c01ae2d812 [+] ENV IN_DOCKER 2024-01-17 05:37:16 +03:00
9 changed files with 377 additions and 226 deletions

View File

@ -5,6 +5,7 @@ WORKDIR /app
COPY requirements.txt . COPY requirements.txt .
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
ENV IN_DOCKER=1
COPY ./src . COPY ./src .
CMD [ "python", "./main.py" ] CMD [ "python", "./main.py" ]

163
README.md
View File

@ -1,26 +1,37 @@
# RCON бот для ВК сообществ # RCON бот для ВК сообществ
[![CodeTime badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fapi.codetime.dev%2Fshield%3Fid%3D24004%26project%3D%26in%3D0)](https://github.com/SantaSpeen/)
**Не забывай про звёздочку!)** **Не забывай про звёздочку!)**
## Что умеет: ## Что умеет:
### Команды ### Команды
<p style="color: red">Не стесняйтесь, предлагайте свои идеи в issue, Vk, Telegram</p> **Не стесняйтесь, предлагайте свои идеи в issue, Vk, Telegram**\
Бот использует по умолчанию хост - *default*, т.е. `.rcon default say hello` такое же что и `.rcon say hello`
* Доступные разрешённым людям * **`.rcon (<host> | default) <command>`** - Исполняет <*command*> на <*host*> и показывает ответ сервера (`bot.rcon.<host>` и `bot.rcon.<host>.<command>`)
* **`.rcon <command>`** - Исполняет <*command*> и показывает ответ сервера
* **`.bot`** - Команды бота, требует разрешения `bot`
* Доступные всем * **`.bot`** - Команды бота (`bot.help`)
* **`!help`** - Выводит страничку с командами (Текст в файле help_message.txt) * **`.bot help`** - Команды бота (`bot.help`)
* **`!online`** - Запрашивает у сервера онлайн и выводит * **`.bot info`** - Выводит краткую информацию о боте. (`bot.info`)
* **`!id`** - Выводит ID пользователя, и его роль * **`.bot hosts list`** - Список доступных хостов. (`bot.hosts.list`)
* **`.bot hosts reload`** - Перезагружает hosts.yml. (`bot.hosts.reload`)
* **`.bot perms reload`** - Перезагружает permissions.yml. (`bot.perms.reload`)
* **`!help`** - Выводит содержимое help_message.txt (`bot.cmd.help`)
* **`!id`** - Выводит ID пользователя, его роль и ник (`bot.cmd.id`)
* **`!online (<host> | default)`** - Запрашивает у <*host*> онлайн и выводит (`bot.cmd.online.<host>`)
* **`!history (<host> | default)`** - **(WIP)** Выводит график онлайна на <*host*> (`bot.cmd.history.<host>`)
### Возможности ### Возможности
* Система [permissions](#система-permissions): * Работа в [Docker](./Dockerfile)
* Локально
* Интеграция с LuckPerms (В разработке)
* Система [Permissions](#система-permissions) - Разрешения для пользователей
* Система [MultiHost](#система-multihost) - Если у тебя очень много хостов
## Как запустить? ## Как запустить?
@ -40,58 +51,118 @@
_Всё очень легко и просто)_ _Всё очень легко и просто)_
## Система permissions ## Система Permissions
Файл: `config/permissions.yml`\
В файле `permissions.yml` указаны все пользователи с "повышенным" уровнем доступа к боту\ В файле указаны **vk id** пользователей и их разрешения\
Пример Стандартный вид:
```yaml ```yaml
noRole: Нет роли noRole: Нет роли
noRights: Нет прав # null для отключения noRights: Нет прав # null для отключения
noNick: Не указан # Используется для !id, ник берётся из LuckPerms.nicks независимо от useLuckPerms noNick: Не указан # Используется для !id
# Таблица соответствия vkID к нику в Майнкрафте
# Ник будет передаваться в плагины (Плагины бота)
nicks:
370926160: Rick
583018016: SantaSpeen
perms: perms:
admins: # Имя группы admin: # Имя группы
name: Админ # Имя группы, которое будет отображаться в боте name: Админ # Имя группы, которое будет отображаться в боте
ids: # вк ИД входящих в состав группы ids: # вк ИД входящих в состав группы
- 370926160 - 370926160
parent: # Наследование прав
- helper
allow: # Какие команды разрешены, "*" - все allow: # Какие команды разрешены, "*" - все
- '*' - '*'
# Пример настройки helper:
helpers:
name: Хелпер name: Хелпер
ids: ids:
- 583018016 - 583018016
allow: allow:
- bot.rcon.* # См. host.yml
- say - say
- mute - mute
- warn - warn
default:
# Находится в режиме тестирования name: Игрок
# Интеграция с базой данных LuckPerms (Нужна именно внешняя база данных) allow:
useLuckPerms: false - bot.cmd.help
LuckPerms: - bot.cmd.id
- bot.cmd.online.*
# Таблица соответствия vkID к нику в Майнкрафте - bot.cmd.history.*
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
``` ```
**Интеграция с LuckPerms ещё не готова!** ## Система MultiHost
Файл: `config/hosts.yml`\
Тут должно быть описание..\
Стандартный вид:
```yaml
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
```
### За помощью, заказами и предложениями можно обратиться сюда: ### За помощью, заказами и предложениями можно обратиться сюда:

View File

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

View File

@ -1,7 +1,9 @@
import sys
from pathlib import Path from pathlib import Path
import requests import requests
import vk import vk
from easydict import EasyDict
from loguru import logger from loguru import logger
import modules import modules
@ -24,7 +26,7 @@ class Bot:
logger.info(f"[BOT] ID группы: {self.group_id}") logger.info(f"[BOT] ID группы: {self.group_id}")
def _test(self): def _test(self):
Permissions.perm_file = Path(config["perms_file"]) Permissions.perms_file = Path(config["perms_file"])
self.perms = Permissions.load() self.perms = Permissions.load()
self.hosts = Hosts.load() self.hosts = Hosts.load()
# Check token # Check token
@ -47,142 +49,164 @@ class Bot:
else: else:
self.vk.messages.send(message=message, peer_id=peer_id, random_id=0) self.vk.messages.send(message=message, peer_id=peer_id, random_id=0)
def _handle_rcon(self, message, _write=True, allow=False): def _handle_bot(self, message, **_):
"""Проверка прав и выполнение RCON команды""" cmds = ("Доступные команды:\n"
from_id = message['from_id'] " .bot help - Вывести это сообщение.\n"
peer_id = message['peer_id'] " .bot info - Выводит краткую информацию о боте.\n"
text = message['text'] " .bot hosts list - Список доступных хостов\n"
logger.info(f"[BOT] {peer_id}:{from_id}:{text}") " .bot hosts reload - Перезагружает hosts.yml\n"
tsplit = text.split(" ") # " .bot perms user [add | del] <group> - (WIP) \n"
if allow: # " .bot perms list - (WIP) Выводит список групп \n"
role = "console" " .bot perms reload - Перезагружает permissions.yml")
else: tsplit = message.text.split(" ")
if tsplit[1] in self.hosts.hosts: if len(tsplit) == 1:
props = {"cmd": " ".join(tsplit[2:]), "server": tsplit[1]} if not message.has_perm("bot.help"): return
else: message.reply(cmds)
props = {"cmd": " ".join(tsplit[1:])} return
allow, role = self.perms.is_allowed(from_id, props['cmd']) match tsplit[1]:
if allow: case "hosts":
answer, _ = self.hosts.rcon(**props) if not message.has_perm(["bot.hosts", "bot.hosts.*", "bot.hosts.reload", "bot.hosts.list"]): return
if not answer: match tsplit[2] if len(tsplit) > 2 else None:
answer = "Выполнено без ответа." case "list":
logger.info(f"[BOT] User: {from_id}({role}) in Chat: {peer_id} use RCON cmd: \"{props['cmd']}\", " if not message.has_perm(["bot.hosts.*", "bot.hosts.list"]): return
f"with answer: \"{answer}\"") s = ""
if _write: for host in self.hosts.hosts:
self.write(peer_id, ("" if not props.get("server") else f"Ответ от {self.hosts._hosts_meta[props["server"]].get("name", props["server"])}:\n") + answer) ping = 0
else: r, e = self.hosts.mine(host, True)
return answer if not e:
else: ping = r.latency
logger.info(f"[BOT] User: {from_id}({role}) in Chat: {peer_id} no have rights RCON cmd: \"{props['cmd']}\".") _, e = self.hosts.rcon("list", host, True)
if self.perms.no_rights: # Если есть текст meta = self.hosts._hosts_meta[host]
self.write(peer_id, self.perms.no_rights) 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:
# noinspection SpellCheckingInspection
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}")
message.reply("Список хостов:" + s)
case "reload":
if not message.has_perm(["bot.hosts.*", "bot.hosts.reload"]): return
self.hosts = Hosts.load()
message.reply("hosts.yml - Загружен")
case _:
message.reply(".bot hosts [list | reload]")
case "perms":
if not message.has_perm(["bot.perms", "bot.perms.*", "bot.perms.reload"]): return
match tsplit[2] if len(tsplit) > 2 else None:
case "reload":
if not message.has_perm(["bot.perms.*", "bot.perms.reload"]): return
self.perms = Permissions.load()
message.reply("permissions.yml - Загружен")
case _:
message.reply(".bot perms [reload]")
case "info":
if not message.has_perm(["bot.info"]): return
message.reply(f"RconVkBot\n"
f"Версия бота: {modules.__version__}, последняя: {not is_new_version}")
case _:
if not message.has_perm(["bot.help"]): return
message.reply(cmds)
def _handle_bot(self, message): def _handle_rcon(self, message, role, host, text, _write=True):
from_id = message['from_id'] """Проверка прав и выполнение RCON команды"""
if self.perms.is_allowed(from_id, "bot"): if len(text) == 0: return
peer_id = message['peer_id'] cmd = text.split(" ")[0]
text = message['text'] if not message.has_perm(["bot.rcon.*.*", f"bot.rcon.*.{cmd}", f"bot.rcon.{host}.*", f"bot.rcon.{host}.{cmd}"]):
logger.info(f"[BOT] {peer_id}:{from_id}:{text}") return
tsplit = text.split(" ") answer, _ = self.hosts.rcon(text, host)
cmds = ("Доступные команды:\n" if not answer:
" .bot help - Вывести это сообщение.\n" answer = "Выполнено без ответа."
" .bot info - Выводит краткую информацию о боте.\n" logger.info(f"[BOT] User: {message['from_id']}({role}) in Chat: {message.peer_id} use RCON cmd: \"{text}\", "
" .bot hosts list - Список доступных хостов\n" f"with answer: \"{answer}\"")
" .bot hosts reload - Перезагружает hosts.yml\n" message.reply(("" if host == "default" else f"Ответ от {self.hosts.get_name(host)}:\n") + answer)
# " .bot perms user [add | del] <group> - не реализовано \n"
# " .bot perms list - Выводит список групп \n" def _handle_online(self, message, host, **_):
" .bot perms reload - Перезагружает permissions.yml") server, _ = self.hosts.mine(host)
if len(tsplit) == 1: players = server.players
self.write(peer_id, cmds) message.reply(f"На сервере сейчас {players.online}/{players.max}")
return
match tsplit[1]: def _perm_handler(self, message, perms: list | str, func: callable):
case "hosts": from_id = message.from_id
match tsplit[2] if len(tsplit) > 2 else None: peer_id = message.peer_id
case "list": message.has_perm = lambda x: self.perms.is_allowed(from_id, x)[0]
s = "" if isinstance(perms, str):
for host in self.hosts.hosts: perms = [perms]
ping = 0 host, text = self.hosts.parse_host(message['text'])
r, e = self.hosts.mine(host, True) for i, V in enumerate(perms):
if not e: perms[i] = V.format(host=host)
ping = r.latency allow, role = self.perms.is_allowed(from_id, perms)
_, e = self.hosts.rcon("list", host, True) logger.info(f"[BOT] {host}:{peer_id}:{from_id}:{self.perms.get_role(from_id, True)} {message['text']}")
meta = self.hosts._hosts_meta[host] if allow:
name = meta.get("name") func(message=message, role=role, host=host, text=text)
rcon_ok = meta.get('rcon_ok') else:
mine_ok = meta.get('mine_ok') if self.perms.no_rights: # Если есть текст
if (not rcon_ok and meta['rcon'] > 0) or (not mine_ok and meta['online'] > 0): message.reply(self.perms.no_rights)
s += f"\nㅤ⛔ {host} ({name})"
else:
s += f"\nㅤ✅ {host} ({name})"
if "-a" in tsplit or "--all" in tsplit:
# noinspection SpellCheckingInspection
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): def message_handle(self, message):
from_id = message['from_id'] from_id = message.from_id
peer_id = message['peer_id'] peer_id = message.peer_id
text = message['text'] message.reply = lambda text: self.write(peer_id, text)
match text: sw = lambda t, x: t.startswith(x)
case i if i.startswith(".rcon "): match message.text:
self._handle_rcon(message) case i if sw(i, ".bot"):
case i if i.startswith(".bot"): perms = [
self._handle_bot(message) "bot.help", "bot.info",
"bot.perms", "bot.perms.*", "bot.perms.reload",
"bot.hosts", "bot.hosts.*", "bot.hosts.reload", "bot.hosts.list"
]
self._perm_handler(message, perms, self._handle_bot)
case i if sw(i, ".rcon "):
perms = ["bot.rcon.*", "bot.rcon.{host}"]
self._perm_handler(message, perms, self._handle_rcon)
case "!help": case "!help":
logger.info(f"[BOT] {peer_id}:{from_id}:{text}") perms = ["bot.help"]
self.write(peer_id, self.help_message) self._perm_handler(message, perms, lambda **_: self.write(peer_id, self.help_message))
case "!online": case i if sw(i, "!online"):
logger.info(f"[BOT] {peer_id}:{from_id}:{text}") perms = ["bot.online.*", "bot.online.{host}"]
server, _ = self.hosts.mine() self._perm_handler(message, perms, self._handle_online)
players = server.players
self.write(peer_id, f"На сервере сейчас {players.online}/{players.max}")
case "!id": case "!id":
logger.info(f"[BOT] {peer_id}:{from_id}:{text}") def __id(**_):
self.write(peer_id, self.write(peer_id, ""
f"Твой ID: {from_id}\n" f"Твой ID: {from_id}\n"
f"Роль: {self.perms.get_role(from_id)}\n" f"Роль: {self.perms.get_role(from_id)}\n"
f"Ник: {self.perms.get_nick(from_id)}") f"Ник: {self.perms.get_nick(from_id)}")
self._perm_handler(message, "bot.id", __id)
def listen(self): def listen(self):
server, key, ts = self.get_lp_server() server, key, ts = self.get_lp_server()
session = requests.Session()
logger.info("[BOT] Начинаю получать сообщения..") logger.info("[BOT] Начинаю получать сообщения..")
logger.info("[BOT] {host}:{chat_id}:{user_id}:{role} {text}")
while True: while True:
lp = requests.get(f'{server}?act=a_check&key={key}&ts={ts}&wait=25').json() lp = session.get(f'{server}?act=a_check&key={key}&ts={ts}&wait=3').json()
try: try:
if lp.get('failed') is not None: if lp.get('failed') is not None:
key = self.get_lp_server()[1] key = self.get_lp_server()[1]
if ts != lp.get('ts') and lp.get('updates'): if ts != lp.get('ts') and lp.get('updates'):
updates = lp['updates'][0] updates = lp['updates'][0]
if updates['type'] == "message_new": if updates['type'] == "message_new":
self.message_handle(updates['object']['message']) # noinspection PyTypeChecker
self.message_handle(EasyDict(**updates['object']['message']))
ts = lp.get('ts') ts = lp.get('ts')
except Exception as i: except Exception as i:
ts = lp.get('ts') ts = lp.get('ts')
logger.exception(i) logger.exception(i)
def stop(self): def stop(self, signum=-1, frame=None):
self.hosts.unload() logger.debug(f"{signum=} {frame=}")
if signum == -1:
logger.info("Выход.")
self.hosts.unload()
sys.exit(0)

View File

@ -60,6 +60,21 @@ class Hosts:
self._hosts_meta["connected"] = False self._hosts_meta["connected"] = False
return None, e return None, e
def parse_host(self, s: str | list, index: int = 1) -> tuple[str, str]:
if isinstance(s, str):
s = s.split(" ")
if len(s)-1 >= index:
host = s[index]
if host in self._hosts_meta:
return host, " ".join(s[index+1:])
return "default", " ".join(s[index:])
def get_name(self, host) -> str:
h = self._hosts_meta.get(host)
if h:
return h.get("name", host)
return host
def _connect(self) -> None: def _connect(self) -> None:
if self._hosts is None or len(self._hosts) == 0: if self._hosts is None or len(self._hosts) == 0:
logger.error("[HOSTS] Не найдено ни одного хоста.") logger.error("[HOSTS] Не найдено ни одного хоста.")

View File

@ -1,17 +1,22 @@
import sys import platform
import signal
from loguru import logger from loguru import logger
from core import Bot from core import Bot
from modules import enter_to_exit from modules import enter_to_exit, IN_DOCKER
if __name__ == '__main__': if __name__ == '__main__':
bot = 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)
try: try:
bot.listen() bot.listen()
except KeyboardInterrupt:
logger.info("Exited.")
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

@ -10,9 +10,9 @@ from ruamel.yaml import YAML
yaml = YAML() yaml = YAML()
yaml.default_flow_style = False yaml.default_flow_style = False
IN_DOCKER = "DOCKER_CONTAINER" in os.environ IN_DOCKER = "IN_DOCKER" in os.environ
__version__ = '1.3.1' __version__ = '2.0.0'
raw_config_main = """\ raw_config_main = """\
vk_token: "" vk_token: ""
@ -36,26 +36,50 @@ perms:
admin: # Имя группы admin: # Имя группы
name: Админ # Имя группы, которое будет отображаться в боте name: Админ # Имя группы, которое будет отображаться в боте
ids: # вк ИД входящих в состав группы ids: # вк ИД входящих в состав группы
- 370926160 - 370926160
parent: # Наследование прав parent: # Наследование прав
- helper - helper
allow: # Какие команды разрешены, "*" - все 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: helper:
name: Хелпер name: Хелпер
ids: ids:
- 583018016 - 583018016
allow: allow:
- bot.rcon.* # См. host.yml - bot.rcon.default
- say - bot.rcon.lobby
- mute - bot.rcon.survival
- warn - bot.rcon.*.say
- bot.rcon.*.mute
- bot.rcon.survival.ban
- bot.rcon.survival.tempban
default: default:
name: Игрок name: Игрок
allow: allow:
- bot.online.* # См. host.yml - bot.cmd.help
- bot.history.* # См. host.yml - bot.cmd.id
- bot.cmd.online.*
- bot.cmd.history.*
""" """
raw_config_hosts = """\ raw_config_hosts = """\
@ -71,8 +95,8 @@ hosts:
# Разрешение: bot.rcon.<name>; bot.online.<name>; bot.history.<name> # Разрешение: bot.rcon.<name>; bot.online.<name>; bot.history.<name>
# При запуске бота будет проверка доступности всего # При запуске бота будет проверка доступности всего
rcon: 2 # RCON будет доступен по команде .rcon lobby <cmd> (разрешение: bot.rcon.lobby) rcon: 2 # RCON будет доступен по команде .rcon lobby <cmd> (разрешение: bot.rcon.lobby)
# !online будет доступен по команде !online lobby (разрешение: bot.online.lobby) # !online будет доступен по команде !online lobby (разрешение: bot.cmd.online.lobby)
# !history будет доступен по команде !history lobby (разрешение: bot.history.lobby) # !history будет доступен по команде !history lobby (разрешение: bot.cmd.history.lobby)
online: 2 online: 2
rcon: # RCON подключение rcon: # RCON подключение
host: 192.168.0.31 host: 192.168.0.31
@ -119,17 +143,15 @@ hosts:
important: true important: true
rcon: 0 rcon: 0
online: 1 online: 1
rcon: null rcon:
mine: mine:
host: 192.168.0.31 host: 192.168.0.31
port: 15009 port: 15009
""" """
raw_help = """\ raw_help = """\
Тебе не нужна помощь, ты и так беспомощный, кожаный ублюдок. Так уж и быть, подскажу пару команд... !help - Вывести это сообщение
!help - Вывести это сообщение. !online - Показать текущий онлайн на сервере
!online - Показать текущий онлайн на сервере.
Бот сделан кожанным петухом - админом, все вопросы к нему, я не причём.
""" """
config_dir = "./config/" config_dir = "./config/"
@ -176,9 +198,11 @@ with open(config_file_main) as f:
config = yaml.load(f) config = yaml.load(f)
logger.info("Запуск..") logger.info("Запуск..")
if IN_DOCKER:
logger.info("Обнаружен запуск из DOCKER")
if not os.path.exists(config["help_file"]): if not os.path.exists(config["help_file"]):
logger.info(f"Создание: {config["help_file"]}...") logger.info(f"Создание: {config["help_file"]}...")
with open(config.vk.help_file, "w", encoding="utf-8") as f: with open(config["help_file"], "w", encoding="utf-8") as f:
f.write(raw_help) f.write(raw_help)

View File

@ -7,7 +7,7 @@ from modules import yaml, raw_config_perms, enter_to_exit
class Permissions: class Permissions:
perm_file = Path("permissions.yml") perms_file = Path("permissions.yml")
def __init__(self, **kwargs): def __init__(self, **kwargs):
logger.debug(f"[PERMS] Initializing Permissions") logger.debug(f"[PERMS] Initializing Permissions")
@ -17,7 +17,7 @@ class Permissions:
self.no_rights = kwargs.get("noRights") self.no_rights = kwargs.get("noRights")
self._perms = kwargs.get('perms') self._perms = kwargs.get('perms')
if not self._perms or not isinstance(self._perms, dict): if not self._perms or not isinstance(self._perms, dict):
logger.error(f"[PERMS] Блок: {"perms"!r}, в {self.perm_file!r} - Не валидный") logger.error(f"[PERMS] Блок: {"perms"!r}, в {self.perms_file!r} - Не валидный")
logger.debug(f"perms: {type(self._perms)}") logger.debug(f"perms: {type(self._perms)}")
logger.debug(self._perms) logger.debug(self._perms)
enter_to_exit() enter_to_exit()
@ -26,15 +26,14 @@ class Permissions:
self.__handle_members() self.__handle_members()
logger.info(f"[PERMS] Права загружены") logger.info(f"[PERMS] Права загружены")
def __handle_parents(self, p=None): def __handle_parents(self, r=True):
if p is None: p = {}
p = {}
for parent, v in self._perms.items(): for parent, v in self._perms.items():
for child in v.get("parent", []): for child in v.get("parent", []):
p[child] = parent p[child] = parent
if p.get(child) == parent and p.get(parent) == child: if p.get(child) == parent and p.get(parent) == child:
logger.warning(f"[PERMS] Рекурсивное присваивание запрещено: " logger.warning(f"[PERMS] Рекурсивное присваивание запрещено: "
f"perms.{child}.parent.{parent} - perms.{parent}.parent.{child} ({self.perm_file!r})") f"perms.{child}.parent.{parent} - perms.{parent}.parent.{child} ({self.perms_file!r})")
del p[parent] del p[parent]
for child, parent in p.items(): for child, parent in p.items():
@ -46,6 +45,10 @@ class Permissions:
else: else:
logger.warning(f"[PERMS] Группа {child!r} - не найдена (perms.{parent}.parent.{child})") logger.warning(f"[PERMS] Группа {child!r} - не найдена (perms.{parent}.parent.{child})")
if r:
logger.debug(f"[PERMS] Again :)")
self.__handle_parents(False)
def __handle_members(self): def __handle_members(self):
self.__handle_parents() self.__handle_parents()
for role, role_data in self._perms.items(): for role, role_data in self._perms.items():
@ -64,23 +67,34 @@ class Permissions:
} }
logger.debug(f"{self._members=}") logger.debug(f"{self._members=}")
def is_allowed(self, member: int, _perms: str | list) -> tuple[bool, str]: def is_allowed(self, member: int, perms: str | list, raw_role=False) -> tuple[bool, str]:
if isinstance(_perms, str): if isinstance(perms, str):
_perms = [_perms] perms = [perms]
for perm in _perms: logger.debug(perms)
user = self._members.get(member) allow = False, self._no_role
if user: user = self._members.get(member)
friendly = user['friendly'] if user:
allow = user['allow'] allow_list = user['allow']
if (("*" in allow) or (perm in allow)) and (f"-{perm}" not in allow): logger.debug(f"{user=} {allow_list=}")
return True, friendly role = self.get_role(member, raw_role)
return False, friendly for perm in perms:
return False, self._no_role if f"-{perm}" in allow_list:
allow = False, role
logger.debug(f"Found -{perm=}")
break
if not allow[0]:
if ((("*" in allow_list) or ("bot.*" in allow_list) or (perm in allow_list))
and (f"-{perm}" not in allow_list)):
allow = True, role
else:
allow = False, role
logger.debug(allow)
return allow
def get_role(self, member): def get_role(self, member, raw_role=False):
u = self._members.get(member) user = self._members.get(member)
if u: if user:
return u['friendly'] return user['friendly'] if not raw_role else user['role']
return self._no_role return self._no_role
def get_nick(self, member): def get_nick(self, member):
@ -91,20 +105,16 @@ class Permissions:
@classmethod @classmethod
def load(cls): def load(cls):
if os.path.exists(cls.perm_file): if os.path.exists(cls.perms_file):
data = yaml.load(cls.perm_file) data = yaml.load(cls.perms_file)
if not data: if not data:
os.remove(cls.perm_file) os.remove(cls.perms_file)
return Permissions.load() return Permissions.load()
else: else:
logger.info(f"Создание: {cls.perm_file}...") logger.info(f"Создание: {cls.perms_file}...")
data = yaml.load(raw_config_perms) data = yaml.load(raw_config_perms)
with open(cls.perm_file, mode="w", encoding="utf-8") as f: with open(cls.perms_file, mode="w", encoding="utf-8") as f:
yaml.dump(data, f) yaml.dump(data, f)
logger.info(f"[PERMS] {cls.perm_file} - загружен") logger.info(f"[PERMS] {cls.perms_file} - загружен")
return Permissions(**data) return Permissions(**data)
if __name__ == '__main__':
perms = Permissions.load()

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.1 Version: 2.0.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