custom emoji in inline buttons/ CLI 0.3.9/ updated dependencies/ other minor changes
This commit is contained in:
@@ -20,4 +20,7 @@ RUN rm -rf /app/venv \
|
|||||||
&& /app/venv/bin/pip install --upgrade pip \
|
&& /app/venv/bin/pip install --upgrade pip \
|
||||||
&& /app/venv/bin/pip install -r requirements.txt
|
&& /app/venv/bin/pip install -r requirements.txt
|
||||||
|
|
||||||
|
RUN adduser --disabled-password --gecos "" appuser && chown -R appuser:appuser /app
|
||||||
|
USER appuser
|
||||||
|
|
||||||
CMD ["/app/venv/bin/python", "main.py"]
|
CMD ["/app/venv/bin/python", "main.py"]
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ from aiogram.fsm.storage.memory import MemoryStorage
|
|||||||
|
|
||||||
from config import API_TOKEN
|
from config import API_TOKEN
|
||||||
from filters.private import IsPrivateFilter
|
from filters.private import IsPrivateFilter
|
||||||
|
from utils.button_icons import apply_button_icons_patch, set_button_icon_config
|
||||||
from utils.custom_emojis import initialize_custom_emojis
|
from utils.custom_emojis import initialize_custom_emojis
|
||||||
from utils.errors import setup_error_handlers
|
from utils.errors import setup_error_handlers
|
||||||
from utils.modules_loader import load_modules_from_folder, modules_hub
|
from utils.modules_loader import load_modules_from_folder, modules_hub
|
||||||
|
|
||||||
|
apply_button_icons_patch()
|
||||||
|
|
||||||
bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
|
bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
|
||||||
storage = MemoryStorage()
|
storage = MemoryStorage()
|
||||||
@@ -18,6 +20,10 @@ dp.include_router(modules_hub)
|
|||||||
|
|
||||||
load_modules_from_folder()
|
load_modules_from_folder()
|
||||||
|
|
||||||
|
from handlers.buttons import BUTTON_ICON_CONFIG
|
||||||
|
|
||||||
|
set_button_icon_config(BUTTON_ICON_CONFIG)
|
||||||
|
|
||||||
dp.message.filter(IsPrivateFilter())
|
dp.message.filter(IsPrivateFilter())
|
||||||
dp.callback_query.filter(IsPrivateFilter())
|
dp.callback_query.filter(IsPrivateFilter())
|
||||||
|
|
||||||
|
|||||||
+15
-11
@@ -210,7 +210,10 @@ def restore_from_backup():
|
|||||||
install_rsync_if_needed()
|
install_rsync_if_needed()
|
||||||
|
|
||||||
console.print("[yellow]Копирую файлы из бэкапа в проект...[/yellow]")
|
console.print("[yellow]Копирую файлы из бэкапа в проект...[/yellow]")
|
||||||
rc = subprocess.run(f"rsync -a --delete {sel_path}/ {PROJECT_DIR}/", shell=True).returncode
|
rc = subprocess.run(
|
||||||
|
["rsync", "-a", "--delete", f"{sel_path}/", f"{PROJECT_DIR}/"],
|
||||||
|
check=False,
|
||||||
|
).returncode
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
console.print("[red]❌ Ошибка rsync при восстановлении[/red]")
|
console.print("[red]❌ Ошибка rsync при восстановлении[/red]")
|
||||||
return
|
return
|
||||||
@@ -239,7 +242,7 @@ def auto_update_cli():
|
|||||||
console.print("[green]Доступна новая версия CLI. Обновляю...[/green]")
|
console.print("[green]Доступна новая версия CLI. Обновляю...[/green]")
|
||||||
with open(current_path, "w", encoding="utf-8") as f:
|
with open(current_path, "w", encoding="utf-8") as f:
|
||||||
f.write(latest_text)
|
f.write(latest_text)
|
||||||
os.chmod(current_path, 0o755)
|
os.chmod(current_path, 0o644)
|
||||||
console.print("[green]CLI обновлён. Перезапуск...[/green]")
|
console.print("[green]CLI обновлён. Перезапуск...[/green]")
|
||||||
os.execv(sys.executable, [sys.executable, current_path])
|
os.execv(sys.executable, [sys.executable, current_path])
|
||||||
else:
|
else:
|
||||||
@@ -370,13 +373,13 @@ def install_dependencies():
|
|||||||
shutil.rmtree("venv")
|
shutil.rmtree("venv")
|
||||||
console.print("[yellow]Удалён старый venv[/yellow]")
|
console.print("[yellow]Удалён старый venv[/yellow]")
|
||||||
|
|
||||||
subprocess.run(f"{python312_path} -m venv venv", shell=True, check=True)
|
subprocess.run([python312_path, "-m", "venv", "venv"], check=True)
|
||||||
|
|
||||||
progress.update(task_id, description="Установка зависимостей...")
|
progress.update(task_id, description="Установка зависимостей...")
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
"bash -c 'source venv/bin/activate && pip install -r requirements.txt'",
|
[os.path.join("venv", "bin", "pip"), "install", "-r", "requirements.txt"],
|
||||||
shell=True,
|
|
||||||
check=True,
|
check=True,
|
||||||
|
cwd=PROJECT_DIR,
|
||||||
)
|
)
|
||||||
|
|
||||||
progress.update(task_id, description="Установка завершена")
|
progress.update(task_id, description="Установка завершена")
|
||||||
@@ -390,7 +393,7 @@ def restart_service():
|
|||||||
if is_service_exists(SERVICE_NAME):
|
if is_service_exists(SERVICE_NAME):
|
||||||
console.print("[blue]🚀 Перезапуск службы...[/blue]")
|
console.print("[blue]🚀 Перезапуск службы...[/blue]")
|
||||||
with console.status("[bold yellow]Перезапуск...[/bold yellow]"):
|
with console.status("[bold yellow]Перезапуск...[/bold yellow]"):
|
||||||
subprocess.run(f"sudo systemctl restart {SERVICE_NAME}", shell=True)
|
subprocess.run(["sudo", "systemctl", "restart", SERVICE_NAME])
|
||||||
else:
|
else:
|
||||||
console.print(f"[red]❌ Служба {SERVICE_NAME} не найдена.[/red]")
|
console.print(f"[red]❌ Служба {SERVICE_NAME} не найдена.[/red]")
|
||||||
|
|
||||||
@@ -482,7 +485,8 @@ def update_from_beta():
|
|||||||
exclude_options += "--exclude=handlers/buttons.py "
|
exclude_options += "--exclude=handlers/buttons.py "
|
||||||
exclude_options += "--exclude=modules "
|
exclude_options += "--exclude=modules "
|
||||||
|
|
||||||
subprocess.run(f"rsync -a {exclude_options} {TEMP_DIR}/ {PROJECT_DIR}/", shell=True)
|
rsync_cmd = ["rsync", "-a"] + [x for x in exclude_options.split() if x] + [f"{TEMP_DIR}/", f"{PROJECT_DIR}/"]
|
||||||
|
subprocess.run(rsync_cmd)
|
||||||
|
|
||||||
modules_path = os.path.join(PROJECT_DIR, "modules")
|
modules_path = os.path.join(PROJECT_DIR, "modules")
|
||||||
if not os.path.exists(modules_path):
|
if not os.path.exists(modules_path):
|
||||||
@@ -544,8 +548,7 @@ def update_from_release():
|
|||||||
console.print(f"[cyan]Клонируем релиз {tag_name} во временную папку...[/cyan]")
|
console.print(f"[cyan]Клонируем релиз {tag_name} во временную папку...[/cyan]")
|
||||||
subprocess.run(["rm", "-rf", TEMP_DIR])
|
subprocess.run(["rm", "-rf", TEMP_DIR])
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
f"git clone --branch {tag_name} {GITHUB_REPO} {TEMP_DIR}",
|
["git", "clone", "--branch", tag_name, GITHUB_REPO, TEMP_DIR],
|
||||||
shell=True,
|
|
||||||
check=True,
|
check=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -560,7 +563,8 @@ def update_from_release():
|
|||||||
exclude_options += "--exclude=handlers/buttons.py "
|
exclude_options += "--exclude=handlers/buttons.py "
|
||||||
exclude_options += "--exclude=modules "
|
exclude_options += "--exclude=modules "
|
||||||
|
|
||||||
subprocess.run(f"rsync -a {exclude_options} {TEMP_DIR}/ {PROJECT_DIR}/", shell=True)
|
rsync_cmd = ["rsync", "-a"] + exclude_options.split() + [f"{TEMP_DIR}/", f"{PROJECT_DIR}/"]
|
||||||
|
subprocess.run(rsync_cmd)
|
||||||
|
|
||||||
modules_path = os.path.join(PROJECT_DIR, "modules")
|
modules_path = os.path.join(PROJECT_DIR, "modules")
|
||||||
if not os.path.exists(modules_path):
|
if not os.path.exists(modules_path):
|
||||||
@@ -608,7 +612,7 @@ def show_update_menu():
|
|||||||
|
|
||||||
|
|
||||||
def show_menu():
|
def show_menu():
|
||||||
table = Table(title="Solobot CLI v0.3.8", title_style="bold magenta", header_style="bold blue")
|
table = Table(title="Solobot CLI v0.3.9", title_style="bold magenta", header_style="bold blue")
|
||||||
table.add_column("№", justify="center", style="cyan", no_wrap=True)
|
table.add_column("№", justify="center", style="cyan", no_wrap=True)
|
||||||
table.add_column("Операция", style="white")
|
table.add_column("Операция", style="white")
|
||||||
table.add_row("1", "Запустить бота (systemd)")
|
table.add_row("1", "Запустить бота (systemd)")
|
||||||
|
|||||||
@@ -161,7 +161,10 @@ async def get_total_referral_bonus(session: AsyncSession, referrer_tg_id: int, m
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await session.execute(text(bonus_query), {"tg_id": referrer_tg_id, "max_levels": max_levels})
|
result = await session.execute(
|
||||||
|
text(bonus_query), # nosemgrep: python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text
|
||||||
|
{"tg_id": referrer_tg_id, "max_levels": max_levels},
|
||||||
|
)
|
||||||
total_bonus_raw = result.scalar()
|
total_bonus_raw = result.scalar()
|
||||||
total_bonus = round(float(total_bonus_raw or 0), 2)
|
total_bonus = round(float(total_bonus_raw or 0), 2)
|
||||||
|
|
||||||
@@ -189,7 +192,10 @@ async def get_referrals_by_level(session: AsyncSession, referrer_tg_id: int, max
|
|||||||
GROUP BY level
|
GROUP BY level
|
||||||
ORDER BY level
|
ORDER BY level
|
||||||
"""
|
"""
|
||||||
result = await session.execute(text(query), {"referrer_tg_id": referrer_tg_id, "max_levels": max_levels})
|
result = await session.execute(
|
||||||
|
text(query), # nosemgrep: python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text
|
||||||
|
{"referrer_tg_id": referrer_tg_id, "max_levels": max_levels},
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
row["level"]: {
|
row["level"]: {
|
||||||
"total": row["level_count"],
|
"total": row["level_count"],
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
BUTTON_ICON_CONFIG: dict[str, dict[str, str]] = {
|
||||||
|
"partner": {"icon_custom_emoji_id": "5310169226856644648", "style": "primary"},
|
||||||
|
# Примеры по callback_data:
|
||||||
|
# "profile": {"icon_custom_emoji_id": "5310169226856644648", "style": "primary"}, # синяя
|
||||||
|
# "view_keys": {"icon_custom_emoji_id": "5310169226856644648", "style": "success"}, # зеленая
|
||||||
|
# "pay": {"style": "primary"},
|
||||||
|
# "cancel_broadcast": {"style": "danger"}, # красная
|
||||||
|
}
|
||||||
|
|
||||||
# Общие кнопки
|
# Общие кнопки
|
||||||
BACK = "⬅️ Назад"
|
BACK = "⬅️ Назад"
|
||||||
APPLY = "✅ Подтвердить"
|
APPLY = "✅ Подтвердить"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
|
|
||||||
from aiogram import Dispatcher
|
from aiogram import BaseMiddleware, Dispatcher
|
||||||
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
|
||||||
|
|
||||||
from config import CHANNEL_REQUIRED, DISABLE_DIRECT_START
|
from config import CHANNEL_REQUIRED, DISABLE_DIRECT_START
|
||||||
from middlewares.ban_checker import BanCheckerMiddleware
|
from middlewares.ban_checker import BanCheckerMiddleware
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
from aiogram import BaseMiddleware
|
||||||
|
|
||||||
from logger import logger
|
from logger import logger
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -3,15 +3,15 @@ name = "Solo_bot"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aiofiles==24.1.0",
|
"aiofiles==24.1.0",
|
||||||
"aiogram==3.13.1",
|
"aiogram>=3.24.0",
|
||||||
"aiohappyeyeballs==2.4.3",
|
"aiohappyeyeballs>=2.5.0",
|
||||||
"aiohttp==3.10.10",
|
"aiohttp==3.13.3",
|
||||||
"aiosignal==1.3.1",
|
"aiosignal>=1.4.0",
|
||||||
"annotated-types==0.7.0",
|
"annotated-types==0.7.0",
|
||||||
"async-timeout==4.0.3",
|
"async-timeout==4.0.3",
|
||||||
"asyncpg==0.30.0",
|
"asyncpg==0.30.0",
|
||||||
"attrs==24.2.0",
|
"attrs==24.2.0",
|
||||||
"certifi",
|
"certifi>=2023.5.7,<2024.0.0", # ограничение из aiocryptopay; CVE-2024-39689 исправлен в 2024.7.4
|
||||||
"charset-normalizer==3.4.0",
|
"charset-normalizer==3.4.0",
|
||||||
"deprecated==1.2.14",
|
"deprecated==1.2.14",
|
||||||
"distro==1.9.0",
|
"distro==1.9.0",
|
||||||
@@ -23,11 +23,11 @@ dependencies = [
|
|||||||
"propcache==0.2.0",
|
"propcache==0.2.0",
|
||||||
"pydantic",
|
"pydantic",
|
||||||
"pydantic-core",
|
"pydantic-core",
|
||||||
"requests==2.32.3",
|
"requests==2.32.4",
|
||||||
"typing-extensions==4.12.2",
|
"typing-extensions==4.12.2",
|
||||||
"urllib3==2.2.3",
|
"urllib3==2.6.3",
|
||||||
"wrapt==1.16.0",
|
"wrapt==1.16.0",
|
||||||
"yarl==1.15.5",
|
"yarl>=1.17.0,<2.0",
|
||||||
"yookassa==3.3.0",
|
"yookassa==3.3.0",
|
||||||
"loguru",
|
"loguru",
|
||||||
"aiocryptopay",
|
"aiocryptopay",
|
||||||
|
|||||||
+12
-12
@@ -1,9 +1,9 @@
|
|||||||
aiocryptopay==0.4.7
|
aiocryptopay==0.4.7
|
||||||
aiofiles==24.1.0
|
aiofiles==24.1.0
|
||||||
aiogram==3.13.1
|
aiogram==3.24.0
|
||||||
aiohappyeyeballs==2.4.3
|
aiohappyeyeballs==2.5.0
|
||||||
aiohttp==3.10.10
|
aiohttp==3.13.3
|
||||||
aiosignal==1.3.1
|
aiosignal>=1.4.0
|
||||||
alembic==1.16.4
|
alembic==1.16.4
|
||||||
annotated-types==0.7.0
|
annotated-types==0.7.0
|
||||||
anyio==4.8.0
|
anyio==4.8.0
|
||||||
@@ -13,18 +13,18 @@ asyncpg==0.30.0
|
|||||||
attrs==24.2.0
|
attrs==24.2.0
|
||||||
babel==2.17.0
|
babel==2.17.0
|
||||||
cachetools==5.5.1
|
cachetools==5.5.1
|
||||||
certifi==2023.11.17
|
certifi==2023.11.17 # aiocryptopay требует certifi<2024; обновить после выхода совместимой версии aiocryptopay
|
||||||
cffi==1.17.1
|
cffi==1.17.1
|
||||||
charset-normalizer==3.4.0
|
charset-normalizer==3.4.0
|
||||||
click==8.2.2
|
click==8.2.2
|
||||||
cryptography==45.0.5
|
cryptography==45.0.5
|
||||||
Deprecated==1.2.14
|
Deprecated==1.2.14
|
||||||
distro==1.9.0
|
distro==1.9.0
|
||||||
fastapi==0.116.1
|
fastapi==0.128.7
|
||||||
frozenlist==1.4.1
|
frozenlist==1.4.1
|
||||||
greenlet==3.1.1
|
greenlet==3.1.1
|
||||||
h11==0.14.0
|
h11==0.16.0
|
||||||
httpcore==1.0.7
|
httpcore==1.0.9
|
||||||
httpx==0.27.2
|
httpx==0.27.2
|
||||||
idna==3.10
|
idna==3.10
|
||||||
loguru==0.7.3
|
loguru==0.7.3
|
||||||
@@ -48,19 +48,19 @@ Pygments==2.19.2
|
|||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
pytz==2025.1
|
pytz==2025.1
|
||||||
qrcode==8.2
|
qrcode==8.2
|
||||||
requests==2.32.3
|
requests==2.32.4
|
||||||
rich==14.1.0
|
rich==14.1.0
|
||||||
robokassa==0.3.2
|
robokassa==0.3.2
|
||||||
ruff==0.9.5
|
ruff==0.9.5
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
sniffio==1.3.1
|
sniffio==1.3.1
|
||||||
SQLAlchemy==2.0.38
|
SQLAlchemy==2.0.38
|
||||||
starlette==0.47.2
|
starlette==0.49.1
|
||||||
StrEnum==0.4.15
|
StrEnum==0.4.15
|
||||||
typing_extensions==4.12.2
|
typing_extensions==4.12.2
|
||||||
tzlocal==5.3.1
|
tzlocal==5.3.1
|
||||||
urllib3==2.2.3
|
urllib3==2.6.3
|
||||||
uvicorn==0.35.0
|
uvicorn==0.35.0
|
||||||
wrapt==1.16.0
|
wrapt==1.16.0
|
||||||
yarl==1.15.5
|
yarl>=1.17.0,<2.0
|
||||||
yookassa==3.9.0
|
yookassa==3.9.0
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import aiogram.types
|
||||||
|
|
||||||
|
_OriginalInlineKeyboardButton = aiogram.types.InlineKeyboardButton
|
||||||
|
|
||||||
|
_button_icon_config: dict[str, dict[str, str]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def apply_button_icons_patch(config: dict[str, dict[str, str]] | None = None) -> None:
|
||||||
|
"""
|
||||||
|
Патчит InlineKeyboardButton, добавляя поддержку глобального конфига для иконок и стилей кнопок по callback_data или url.
|
||||||
|
"""
|
||||||
|
if config is not None:
|
||||||
|
_button_icon_config.clear()
|
||||||
|
_button_icon_config.update(config)
|
||||||
|
|
||||||
|
class _PatchedInlineKeyboardButton(_OriginalInlineKeyboardButton):
|
||||||
|
def __init__(self, **kwargs: object):
|
||||||
|
key = kwargs.get("callback_data") or kwargs.get("url")
|
||||||
|
if key is not None and isinstance(key, str) and key in _button_icon_config:
|
||||||
|
kwargs = {**kwargs, **_button_icon_config[key]}
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
aiogram.types.InlineKeyboardButton = _PatchedInlineKeyboardButton
|
||||||
|
|
||||||
|
|
||||||
|
def set_button_icon_config(config: dict[str, dict[str, str]]) -> None:
|
||||||
|
"""Подставить конфиг кнопок (вызвать после загрузки handlers, из handlers.buttons.BUTTON_ICON_CONFIG)."""
|
||||||
|
_button_icon_config.clear()
|
||||||
|
_button_icon_config.update(config)
|
||||||
|
|
||||||
|
|
||||||
|
def inline_button(
|
||||||
|
text: str,
|
||||||
|
callback_data: str | None = None,
|
||||||
|
url: str | None = None,
|
||||||
|
web_app: object | None = None,
|
||||||
|
*,
|
||||||
|
icon_custom_emoji_id: str | None = None,
|
||||||
|
style: str | None = None,
|
||||||
|
) -> _OriginalInlineKeyboardButton:
|
||||||
|
"""Собирает InlineKeyboardButton с опциональной иконкой (custom emoji) и стилем."""
|
||||||
|
kwargs: dict = {"text": text}
|
||||||
|
if callback_data is not None:
|
||||||
|
kwargs["callback_data"] = callback_data
|
||||||
|
if url is not None:
|
||||||
|
kwargs["url"] = url
|
||||||
|
if web_app is not None:
|
||||||
|
kwargs["web_app"] = web_app
|
||||||
|
if icon_custom_emoji_id is not None:
|
||||||
|
kwargs["icon_custom_emoji_id"] = icon_custom_emoji_id
|
||||||
|
if style is not None:
|
||||||
|
kwargs["style"] = style
|
||||||
|
return aiogram.types.InlineKeyboardButton(**kwargs)
|
||||||
@@ -12,12 +12,18 @@ from .modules_manager import manager
|
|||||||
|
|
||||||
modules_hub = Router(name="modules_hub")
|
modules_hub = Router(name="modules_hub")
|
||||||
|
|
||||||
|
def _is_safe_module_name(name: str) -> bool:
|
||||||
|
return bool(name and name.isidentifier() and "." not in name and "/" not in name and "\\" not in name)
|
||||||
|
|
||||||
|
|
||||||
def load_modules_from_folder(folder: str = "modules") -> list[Router]:
|
def load_modules_from_folder(folder: str = "modules") -> list[Router]:
|
||||||
routers = []
|
routers = []
|
||||||
base_path = Path(folder)
|
base_path = Path(folder)
|
||||||
|
|
||||||
for _finder, name, _ispkg in pkgutil.iter_modules([str(base_path)]):
|
for _finder, name, _ispkg in pkgutil.iter_modules([str(base_path)]):
|
||||||
|
if not _is_safe_module_name(name):
|
||||||
|
logger.warning(f"[Modules] Пропуск недопустимого имени модуля: {name!r}")
|
||||||
|
continue
|
||||||
if not manager.should_autostart(name):
|
if not manager.should_autostart(name):
|
||||||
logger.info(f"[Modules] Пропуск автозапуска модуля '{name}' (отключён).")
|
logger.info(f"[Modules] Пропуск автозапуска модуля '{name}' (отключён).")
|
||||||
continue
|
continue
|
||||||
@@ -43,6 +49,8 @@ def load_module_webhooks(folder: str = "modules") -> list[dict]:
|
|||||||
base_path = Path(folder)
|
base_path = Path(folder)
|
||||||
|
|
||||||
for _finder, name, _ispkg in pkgutil.iter_modules([str(base_path)]):
|
for _finder, name, _ispkg in pkgutil.iter_modules([str(base_path)]):
|
||||||
|
if not _is_safe_module_name(name):
|
||||||
|
continue
|
||||||
if not manager.should_autostart(name):
|
if not manager.should_autostart(name):
|
||||||
logger.info(f"[Modules] Пропуск вебхуков модуля '{name}' (отключён).")
|
logger.info(f"[Modules] Пропуск вебхуков модуля '{name}' (отключён).")
|
||||||
continue
|
continue
|
||||||
@@ -65,6 +73,8 @@ def load_module_fast_flow_handlers(folder: str = "modules") -> dict:
|
|||||||
base_path = Path(folder)
|
base_path = Path(folder)
|
||||||
|
|
||||||
for _finder, name, _ispkg in pkgutil.iter_modules([str(base_path)]):
|
for _finder, name, _ispkg in pkgutil.iter_modules([str(base_path)]):
|
||||||
|
if not _is_safe_module_name(name):
|
||||||
|
continue
|
||||||
if not manager.should_autostart(name):
|
if not manager.should_autostart(name):
|
||||||
logger.info(f"[Modules] Пропуск fast-flow модуля '{name}' (отключён).")
|
logger.info(f"[Modules] Пропуск fast-flow модуля '{name}' (отключён).")
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -57,7 +57,12 @@ class ModulesManager:
|
|||||||
rec.enabled = True
|
rec.enabled = True
|
||||||
self.registry[name] = rec
|
self.registry[name] = rec
|
||||||
|
|
||||||
|
def _is_safe_module_name(self, name: str) -> bool:
|
||||||
|
return bool(name and name.isidentifier() and "." not in name and "/" not in name and "\\" not in name)
|
||||||
|
|
||||||
async def start(self, name: str) -> None:
|
async def start(self, name: str) -> None:
|
||||||
|
if not self._is_safe_module_name(name):
|
||||||
|
raise ValueError(f"[Modules] Недопустимое имя модуля: {name!r}")
|
||||||
rec = self.registry.get(name) or ModuleRecord(name, self.pkg(name))
|
rec = self.registry.get(name) or ModuleRecord(name, self.pkg(name))
|
||||||
if rec.enabled:
|
if rec.enabled:
|
||||||
logger.info(f"[Modules] {name} уже активен.")
|
logger.info(f"[Modules] {name} уже активен.")
|
||||||
|
|||||||
Reference in New Issue
Block a user