feat: Веб-интерфейс - логин/пароль в .env + API команд

 WEB_ADMIN_LOGIN/PASSWORD в .env.example
 Конфиг web_admin в config.py
 API /api/command - выполнение команд
 API /api/commands - список команд
 Обновлён web_server.py с конфигом

📦 Бекап: backups/versions/v2.1.2_web_auth_2026-02-28/

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Admin 2026-02-28 20:28:31 +00:00
parent af4e7d2c5d
commit fc7484889c
5 changed files with 171 additions and 17 deletions

View file

@ -36,3 +36,9 @@ USE_PROXY=true
PROXY_TYPE=socks5 # socks5 или http
PROXY_HOST=127.0.0.1
PROXY_PORT=10808 # SOCKS5 порт (или 10809 для HTTP)
# ===== ВЕБ-ИНТЕРФЕЙС АДМИНА =====
WEB_ADMIN_LOGIN=admin
WEB_ADMIN_PASSWORD=5nMVkjd5nMVkjd
WEB_ADMIN_HOST=0.0.0.0
WEB_ADMIN_PORT=8000

51
.env.web Normal file
View file

@ -0,0 +1,51 @@
# Telegram Bot Token (получить у @BotFather)
BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
# ID чата дома (можно узнать через @userinfobot или переслав сообщение из чата)
ADMIN_CHAT_ID=-1001234567890
# Ваш Telegram ID (админ) - узнать через @userinfobot
ADMIN_USER_ID=123456789
# Путь к базе данных
DATABASE_PATH=./database/domovoy.db
# Директория для экспортов
EXPORT_DIR=./exports
# Расписание экспорта (cron формат: минута час день месяц день_недели)
# Каждое воскресенье в 03:00
EXPORT_SCHEDULE=0 3 * * 0
# Детект шпионов
SPY_DETECTION_ENABLED=true
# Куда слать уведомления о шпионах (ваш ID)
SPY_ALERT_USER_ID=123456789
# Дополнительные админы (через запятую, если будут)
EXTRA_ADMIN_IDS=
# Логирование
LOG_LEVEL=INFO
LOG_FILE=./logs/bot.log
# ===== ПРОКСИ ДЛЯ ОБХОДА БЛОКИРОВОК РКН =====
# xray на хосте: SOCKS5 порт 10808, HTTP порт 10809
USE_PROXY=true
PROXY_TYPE=socks5 # socks5 или http
PROXY_HOST=127.0.0.1
PROXY_PORT=10808 # SOCKS5 порт (или 10809 для HTTP)
# ===== ВЕБ-ИНТЕРФЕЙС АДМИНА =====
WEB_ADMIN_LOGIN=admin
WEB_ADMIN_PASSWORD=5nMVkjd5nMVkjd
WEB_ADMIN_HOST=0.0.0.0
WEB_ADMIN_PORT=8000
# ===== ВЕБ-ИНТЕРФЕЙС АДМИНА =====
WEB_ADMIN_LOGIN=admin
WEB_ADMIN_PASSWORD=5nMVkjd5nMVkjd
WEB_ADMIN_HOST=0.0.0.0
WEB_ADMIN_PORT=8000

View file

@ -35,6 +35,12 @@ PROXY_TYPE = os.getenv('PROXY_TYPE', 'socks5').lower()
PROXY_HOST = os.getenv('PROXY_HOST', '127.0.0.1')
PROXY_PORT = int(os.getenv('PROXY_PORT', '10808'))
# ===== ВЕБ-ИНТЕРФЕЙС =====
WEB_ADMIN_LOGIN = os.getenv('WEB_ADMIN_LOGIN', 'admin')
WEB_ADMIN_PASSWORD = os.getenv('WEB_ADMIN_PASSWORD', 'admin')
WEB_ADMIN_HOST = os.getenv('WEB_ADMIN_HOST', '0.0.0.0')
WEB_ADMIN_PORT = int(os.getenv('WEB_ADMIN_PORT', '8000'))
# Проверка критичных настроек
if not BOT_TOKEN:
raise ValueError('BOT_TOKEN не настроен! Проверьте .env файл')

View file

@ -10,10 +10,12 @@ logger = logging.getLogger(__name__)
class WebServerService:
"""Сервис для управления веб-сервером"""
def __init__(self, host: str = "0.0.0.0", port: int = 8000):
self.host = host
self.port = port
def __init__(self, host: str = None, port: int = None):
# Импортируем конфиг при инициализации
import config
self.host = host or config.WEB_ADMIN_HOST
self.port = port or config.WEB_ADMIN_PORT
self.server = None
self.thread: Optional[threading.Thread] = None
self.is_running = False
@ -23,22 +25,23 @@ class WebServerService:
if self.is_running:
logger.warning("Веб-сервер уже запущен")
return
try:
import uvicorn
from web.app import app
import config
def run_server():
uvicorn.run(app, host=self.host, port=self.port, log_level="info")
self.thread = threading.Thread(target=run_server, daemon=True)
self.thread.start()
self.is_running = True
logger.info(f"Веб-сервер запущен на http://{self.host}:{self.port}")
logger.info(f" Логин: admin")
logger.info(f" Пароль: {self.port} (ID админа)")
logger.info(f" Логин: {config.WEB_ADMIN_LOGIN}")
logger.info(f" Пароль: {config.WEB_ADMIN_PASSWORD}")
except Exception as e:
logger.error(f"Ошибка запуска веб-сервера: {e}")
self.is_running = False
@ -47,7 +50,7 @@ class WebServerService:
"""Остановить веб-сервер"""
if not self.is_running:
return
try:
self.is_running = False
logger.info("🛑 Веб-сервер остановлен")

View file

@ -42,16 +42,16 @@ security = HTTPBasic()
def get_current_admin(credentials: HTTPBasicCredentials = Depends(security)):
"""Проверка админа"""
correct_username = secrets.compare_digest(credentials.username, "admin")
correct_password = secrets.compare_digest(credentials.password, str(config.ADMIN_USER_ID))
correct_username = secrets.compare_digest(credentials.username, config.WEB_ADMIN_LOGIN)
correct_password = secrets.compare_digest(credentials.password, config.WEB_ADMIN_PASSWORD)
if not (correct_username and correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Неверное имя пользователя или пароль",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username
@ -258,11 +258,99 @@ async def settings_page(request: Request, username: str = Depends(get_current_ad
"bot_token": config.BOT_TOKEN[:20] + "...",
"admin_chat_id": config.ADMIN_CHAT_ID,
"admin_user_id": config.ADMIN_USER_ID,
"use_proxy": config.USE_PROXY
"use_proxy": config.USE_PROXY,
"web_login": config.WEB_ADMIN_LOGIN,
"web_password": config.WEB_ADMIN_PASSWORD
}
})
# =============================================================================
# API - КОМАНДЫ БОТА
# =============================================================================
@app.post("/api/command")
async def api_command(
request: Request,
username: str = Depends(get_current_admin)
):
"""Выполнить команду бота"""
import json
from aiogram import Bot
data = await request.json()
command = data.get('command', '')
args = data.get('args', '')
if not command:
return JSONResponse({"success": False, "error": "Нет команды"})
try:
# Создаём бота
bot = Bot(token=config.BOT_TOKEN)
# Формируем полное сообщение команды
full_command = f"/{command}"
if args:
full_command += f" {args}"
# Отправляем команду в чат админа
result = await bot.send_message(
config.ADMIN_CHAT_ID,
full_command
)
await bot.session.close()
return JSONResponse({
"success": True,
"message": f"Команда /{command} отправлена",
"result_id": result.message_id
})
except Exception as e:
return JSONResponse({
"success": False,
"error": str(e)
}, status_code=500)
@app.get("/api/commands")
async def api_commands_list(username: str = Depends(get_current_admin)):
"""Список доступных команд"""
commands = {
"admin": [
{"cmd": "admin", "desc": "Админ-панель"},
{"cmd": "stats", "desc": "Статистика чата"},
{"cmd": "spies", "desc": "Подозрительные пользователи"},
{"cmd": "users", "desc": "Список жильцов"},
{"cmd": "verify", "desc": "Заявки на верификацию"},
{"cmd": "export", "desc": "Экспорт данных"},
{"cmd": "rating", "desc": "Рейтинг пользователей"},
],
"initiative_group": [
{"cmd": "ig_add", "desc": "Добавить в ИГ"},
{"cmd": "ig_remove", "desc": "Удалить из ИГ"},
{"cmd": "ig_list", "desc": "Список ИГ"},
{"cmd": "ig_stats", "desc": "Статистика ИГ"},
{"cmd": "ig_check", "desc": "Проверка участника"},
{"cmd": "ig_broadcast", "desc": "Рассылка для ИГ"},
],
"events": [
{"cmd": "event_add", "desc": "Добавить событие"},
{"cmd": "events", "desc": "Список событий"},
{"cmd": "participants", "desc": "Участники события"},
{"cmd": "event_cancel", "desc": "Отменить событие"},
],
"security": [
{"cmd": "unverify", "desc": "Отозвать верификацию"},
{"cmd": "unverified_list", "desc": "Список не верифицированных"},
{"cmd": "ban", "desc": "Забанить пользователя"},
{"cmd": "unban", "desc": "Разбанить"},
]
}
return JSONResponse({"commands": commands})
# =============================================================================
# ЗАПУСК
# =============================================================================