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:
parent
af4e7d2c5d
commit
fc7484889c
5 changed files with 171 additions and 17 deletions
|
|
@ -36,3 +36,9 @@ USE_PROXY=true
|
||||||
PROXY_TYPE=socks5 # socks5 или http
|
PROXY_TYPE=socks5 # socks5 или http
|
||||||
PROXY_HOST=127.0.0.1
|
PROXY_HOST=127.0.0.1
|
||||||
PROXY_PORT=10808 # SOCKS5 порт (или 10809 для HTTP)
|
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
51
.env.web
Normal 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
|
||||||
|
|
||||||
|
|
@ -35,6 +35,12 @@ PROXY_TYPE = os.getenv('PROXY_TYPE', 'socks5').lower()
|
||||||
PROXY_HOST = os.getenv('PROXY_HOST', '127.0.0.1')
|
PROXY_HOST = os.getenv('PROXY_HOST', '127.0.0.1')
|
||||||
PROXY_PORT = int(os.getenv('PROXY_PORT', '10808'))
|
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:
|
if not BOT_TOKEN:
|
||||||
raise ValueError('BOT_TOKEN не настроен! Проверьте .env файл')
|
raise ValueError('BOT_TOKEN не настроен! Проверьте .env файл')
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,11 @@ logger = logging.getLogger(__name__)
|
||||||
class WebServerService:
|
class WebServerService:
|
||||||
"""Сервис для управления веб-сервером"""
|
"""Сервис для управления веб-сервером"""
|
||||||
|
|
||||||
def __init__(self, host: str = "0.0.0.0", port: int = 8000):
|
def __init__(self, host: str = None, port: int = None):
|
||||||
self.host = host
|
# Импортируем конфиг при инициализации
|
||||||
self.port = port
|
import config
|
||||||
|
self.host = host or config.WEB_ADMIN_HOST
|
||||||
|
self.port = port or config.WEB_ADMIN_PORT
|
||||||
self.server = None
|
self.server = None
|
||||||
self.thread: Optional[threading.Thread] = None
|
self.thread: Optional[threading.Thread] = None
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
|
@ -27,6 +29,7 @@ class WebServerService:
|
||||||
try:
|
try:
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from web.app import app
|
from web.app import app
|
||||||
|
import config
|
||||||
|
|
||||||
def run_server():
|
def run_server():
|
||||||
uvicorn.run(app, host=self.host, port=self.port, log_level="info")
|
uvicorn.run(app, host=self.host, port=self.port, log_level="info")
|
||||||
|
|
@ -36,8 +39,8 @@ class WebServerService:
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
|
|
||||||
logger.info(f"✅ Веб-сервер запущен на http://{self.host}:{self.port}")
|
logger.info(f"✅ Веб-сервер запущен на http://{self.host}:{self.port}")
|
||||||
logger.info(f" Логин: admin")
|
logger.info(f" Логин: {config.WEB_ADMIN_LOGIN}")
|
||||||
logger.info(f" Пароль: {self.port} (ID админа)")
|
logger.info(f" Пароль: {config.WEB_ADMIN_PASSWORD}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка запуска веб-сервера: {e}")
|
logger.error(f"Ошибка запуска веб-сервера: {e}")
|
||||||
|
|
|
||||||
94
web/app.py
94
web/app.py
|
|
@ -42,8 +42,8 @@ security = HTTPBasic()
|
||||||
|
|
||||||
def get_current_admin(credentials: HTTPBasicCredentials = Depends(security)):
|
def get_current_admin(credentials: HTTPBasicCredentials = Depends(security)):
|
||||||
"""Проверка админа"""
|
"""Проверка админа"""
|
||||||
correct_username = secrets.compare_digest(credentials.username, "admin")
|
correct_username = secrets.compare_digest(credentials.username, config.WEB_ADMIN_LOGIN)
|
||||||
correct_password = secrets.compare_digest(credentials.password, str(config.ADMIN_USER_ID))
|
correct_password = secrets.compare_digest(credentials.password, config.WEB_ADMIN_PASSWORD)
|
||||||
|
|
||||||
if not (correct_username and correct_password):
|
if not (correct_username and correct_password):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -258,11 +258,99 @@ async def settings_page(request: Request, username: str = Depends(get_current_ad
|
||||||
"bot_token": config.BOT_TOKEN[:20] + "...",
|
"bot_token": config.BOT_TOKEN[:20] + "...",
|
||||||
"admin_chat_id": config.ADMIN_CHAT_ID,
|
"admin_chat_id": config.ADMIN_CHAT_ID,
|
||||||
"admin_user_id": config.ADMIN_USER_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})
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# ЗАПУСК
|
# ЗАПУСК
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue