Spaces:
Runtime error
Runtime error
Upload bot.py
Browse files
bot.py
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import logging
|
| 4 |
+
import requests
|
| 5 |
+
import random
|
| 6 |
+
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
| 7 |
+
from telegram.ext import (
|
| 8 |
+
Application,
|
| 9 |
+
CommandHandler,
|
| 10 |
+
CallbackQueryHandler,
|
| 11 |
+
ContextTypes,
|
| 12 |
+
MessageHandler,
|
| 13 |
+
filters,
|
| 14 |
+
JobQueue
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
# Настройка логирования
|
| 18 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
| 19 |
+
logger = logging.getLogger(__name__)
|
| 20 |
+
|
| 21 |
+
# ======================================================================
|
| 22 |
+
# --- ⚠️ КОНФИГУРАЦИЯ ---
|
| 23 |
+
TELEGRAM_TOKEN = "8596622001:AAE7NxgyUEQ-mZqTMolt7Kgs2ouM0QyjdIE"
|
| 24 |
+
WORKER_API_URL = "https://site-3-8fj7.onrender.com"
|
| 25 |
+
# ======================================================================
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# Проверка конфигурации
|
| 29 |
+
if not all([TELEGRAM_TOKEN, WORKER_API_URL]):
|
| 30 |
+
logger.error("ОШИБКА: Не заданы все необходимые переменные конфигурации.")
|
| 31 |
+
exit(1)
|
| 32 |
+
|
| 33 |
+
# --- Глобальное состояние ---
|
| 34 |
+
USER_DATA = {}
|
| 35 |
+
DATA_FILE = "data.json"
|
| 36 |
+
tg_app = None
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
# ----------------------------------------------------------------------
|
| 40 |
+
# ФУНКЦИИ УПРАВЛЕНИЯ ДАННЫМИ
|
| 41 |
+
# ----------------------------------------------------------------------
|
| 42 |
+
|
| 43 |
+
def load_data():
|
| 44 |
+
"""Загружает данные пользователей из файла."""
|
| 45 |
+
global USER_DATA
|
| 46 |
+
if os.path.exists(DATA_FILE):
|
| 47 |
+
try:
|
| 48 |
+
with open(DATA_FILE, 'r') as f:
|
| 49 |
+
USER_DATA = json.load(f)
|
| 50 |
+
# Инициализация недостающих полей для старых пользователей
|
| 51 |
+
for chat_id in list(USER_DATA.keys()):
|
| 52 |
+
if 'version' not in USER_DATA[chat_id]:
|
| 53 |
+
USER_DATA[chat_id]['version'] = "1.20.1"
|
| 54 |
+
if 'server_type' not in USER_DATA[chat_id]:
|
| 55 |
+
USER_DATA[chat_id]['server_type'] = "vanilla"
|
| 56 |
+
if 'state' not in USER_DATA[chat_id]:
|
| 57 |
+
USER_DATA[chat_id]['state'] = "menu"
|
| 58 |
+
except json.JSONDecodeError:
|
| 59 |
+
logger.error("Ошибка декодирования JSON. Файл данных поврежден.")
|
| 60 |
+
USER_DATA = {}
|
| 61 |
+
else:
|
| 62 |
+
logger.warning("Файл данных не найден. Создаю новое пустое состояние.")
|
| 63 |
+
USER_DATA = {}
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def save_data():
|
| 67 |
+
"""Сохраняет данные пользователей в файл."""
|
| 68 |
+
try:
|
| 69 |
+
with open(DATA_FILE, 'w') as f:
|
| 70 |
+
json.dump(USER_DATA, f, indent=4)
|
| 71 |
+
except IOError as e:
|
| 72 |
+
logger.error(f"Ошибка сохранения данных: {e}")
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
# ----------------------------------------------------------------------
|
| 76 |
+
# ФУНКЦИИ API WORKER
|
| 77 |
+
# ----------------------------------------------------------------------
|
| 78 |
+
|
| 79 |
+
async def start_bot_in_worker(chat_id: str, host: str, port: str, username: str, version: str,
|
| 80 |
+
server_type: str) -> bool:
|
| 81 |
+
"""Отправляет запрос на запуск бота в Worker Service."""
|
| 82 |
+
url = f"{WORKER_API_URL}/api/start"
|
| 83 |
+
|
| 84 |
+
payload = {
|
| 85 |
+
"chatId": chat_id,
|
| 86 |
+
"host": host,
|
| 87 |
+
"port": port,
|
| 88 |
+
"username": username,
|
| 89 |
+
"version": version,
|
| 90 |
+
"serverType": server_type
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
try:
|
| 94 |
+
response = requests.post(url, json=payload, timeout=10)
|
| 95 |
+
response.raise_for_status()
|
| 96 |
+
logger.info(f"Worker API: Бот запущен для {chat_id}. Статус: {response.status_code}")
|
| 97 |
+
return True
|
| 98 |
+
except requests.exceptions.RequestException as e:
|
| 99 |
+
logger.error(f"Ошибка при запросе к Worker API (START): {e}")
|
| 100 |
+
return False
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
async def stop_bot_in_worker(chat_id: str) -> bool:
|
| 104 |
+
"""Отправляет запрос на остановку бота в Worker Service."""
|
| 105 |
+
url = f"{WORKER_API_URL}/api/stop"
|
| 106 |
+
payload = {"chatId": chat_id}
|
| 107 |
+
try:
|
| 108 |
+
response = requests.post(url, json=payload, timeout=5)
|
| 109 |
+
response.raise_for_status()
|
| 110 |
+
logger.info(f"Worker API: Бот остановлен для {chat_id}. Статус: {response.status_code}")
|
| 111 |
+
return True
|
| 112 |
+
except requests.exceptions.RequestException as e:
|
| 113 |
+
logger.error(f"Ошибка при запросе к Worker API (STOP): {e}")
|
| 114 |
+
return False
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
async def get_bot_status(chat_id: str) -> bool:
|
| 118 |
+
"""Получает статус бота из Worker Service."""
|
| 119 |
+
url = f"{WORKER_API_URL}/api/status/{chat_id}"
|
| 120 |
+
try:
|
| 121 |
+
response = requests.get(url, timeout=5)
|
| 122 |
+
response.raise_for_status()
|
| 123 |
+
return response.json().get('isRunning', False)
|
| 124 |
+
except requests.exceptions.RequestException as e:
|
| 125 |
+
logger.error(f"Ошибка при запросе к Worker API (STATUS): {e}")
|
| 126 |
+
return False
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
async def send_command_to_bot(chat_id: str, command: str) -> bool:
|
| 130 |
+
"""Отправляет команду чата Mineflayer-боту."""
|
| 131 |
+
url = f"{WORKER_API_URL}/api/command"
|
| 132 |
+
payload = {"chatId": chat_id, "command": command}
|
| 133 |
+
try:
|
| 134 |
+
response = requests.post(url, json=payload, timeout=5)
|
| 135 |
+
response.raise_for_status()
|
| 136 |
+
return True
|
| 137 |
+
except requests.exceptions.RequestException as e:
|
| 138 |
+
logger.error(f"Ошибка при запросе к Worker API (COMMAND): {e}")
|
| 139 |
+
return False
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
# ----------------------------------------------------------------------
|
| 143 |
+
# 🕵️ ANTI-KICK (ANTI-AFK) JOB
|
| 144 |
+
# ----------------------------------------------------------------------
|
| 145 |
+
|
| 146 |
+
async def anti_afk_job(context: ContextTypes.DEFAULT_TYPE):
|
| 147 |
+
"""
|
| 148 |
+
Эта функция запускается каждые 45 секунд.
|
| 149 |
+
Она проверяет всех активных ботов и отправляет сообщение в чат,
|
| 150 |
+
чтобы сервер не кикнул за AFK.
|
| 151 |
+
"""
|
| 152 |
+
global USER_DATA
|
| 153 |
+
|
| 154 |
+
# Фразы для спама, чтобы админы не сразу спалили, что это бот (или наоборот)
|
| 155 |
+
afk_phrases = [
|
| 156 |
+
"Anti-AFK",
|
| 157 |
+
"Stay alive",
|
| 158 |
+
"Working...",
|
| 159 |
+
"Not AFK",
|
| 160 |
+
"Server check",
|
| 161 |
+
"Bot active"
|
| 162 |
+
]
|
| 163 |
+
|
| 164 |
+
# Проходимся по всем пользователям
|
| 165 |
+
for chat_id in list(USER_DATA.keys()):
|
| 166 |
+
try:
|
| 167 |
+
# Спрашиваем у воркера: "Эй, бот для этого чела жив?"
|
| 168 |
+
is_running = await get_bot_status(chat_id)
|
| 169 |
+
|
| 170 |
+
if is_running:
|
| 171 |
+
# Если бот жив, кидаем рандомную фразу в чат майнкрафта
|
| 172 |
+
msg = random.choice(afk_phrases)
|
| 173 |
+
# Можно также отправлять команду прыжка, если воркер поддерживает,
|
| 174 |
+
# но чат - самый надежный способ.
|
| 175 |
+
await send_command_to_bot(chat_id, msg)
|
| 176 |
+
logger.info(f"[Anti-AFK] Сообщение '{msg}' отправлено для {chat_id}")
|
| 177 |
+
|
| 178 |
+
except Exception as e:
|
| 179 |
+
logger.error(f"[Anti-AFK] Ошибка для {chat_id}: {e}")
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
# ----------------------------------------------------------------------
|
| 183 |
+
# ФУНКЦИИ TELEGRAM
|
| 184 |
+
# ----------------------------------------------------------------------
|
| 185 |
+
|
| 186 |
+
def escape_markdown_v2(text: str, ignore_in_code_block: bool = False) -> str:
|
| 187 |
+
"""Экранирует специальные символы для MarkdownV2."""
|
| 188 |
+
specials = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']
|
| 189 |
+
escaped_text = ""
|
| 190 |
+
in_code_block = False
|
| 191 |
+
i = 0
|
| 192 |
+
while i < len(text):
|
| 193 |
+
char = text[i]
|
| 194 |
+
if char == '`':
|
| 195 |
+
if text[i:i + 3] == '```':
|
| 196 |
+
escaped_text += '```'
|
| 197 |
+
i += 3
|
| 198 |
+
in_code_block = not in_code_block
|
| 199 |
+
continue
|
| 200 |
+
elif text[i:i + 1] == '`':
|
| 201 |
+
escaped_text += '`'
|
| 202 |
+
i += 1
|
| 203 |
+
in_code_block = not in_code_block
|
| 204 |
+
continue
|
| 205 |
+
|
| 206 |
+
if in_code_block and ignore_in_code_block:
|
| 207 |
+
escaped_text += char
|
| 208 |
+
elif char in specials:
|
| 209 |
+
escaped_text += '\\' + char
|
| 210 |
+
else:
|
| 211 |
+
escaped_text += char
|
| 212 |
+
i += 1
|
| 213 |
+
return escaped_text.replace(r'\\`', '`')
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def get_main_menu_keyboard(chat_id_str: str, is_running: bool, is_setting_server: bool = False,
|
| 217 |
+
is_setting_version: bool = False, is_setting_type: bool = False) -> InlineKeyboardMarkup:
|
| 218 |
+
"""Генерирует основное меню."""
|
| 219 |
+
data = USER_DATA.get(chat_id_str, {})
|
| 220 |
+
host = data.get('host', 'Нет')
|
| 221 |
+
port = data.get('port', '25565')
|
| 222 |
+
host_port = f"{host}:{port}"
|
| 223 |
+
username = data.get('username', 'MineflayerBot')
|
| 224 |
+
version = data.get('version', '1.20.1')
|
| 225 |
+
server_type = data.get('server_type', 'vanilla')
|
| 226 |
+
|
| 227 |
+
status_text = "🟢 Бот Активен" if is_running else "🔴 Бот Неактивен"
|
| 228 |
+
start_stop_text = "🛑 Остановить Бота" if is_running else "▶️ Запустить Бота"
|
| 229 |
+
|
| 230 |
+
keyboard = []
|
| 231 |
+
keyboard.append([InlineKeyboardButton(status_text, callback_data='status')])
|
| 232 |
+
|
| 233 |
+
settings_buttons = [
|
| 234 |
+
InlineKeyboardButton(f"🔗 Сервер: {host_port}",
|
| 235 |
+
callback_data='set_server' if not is_setting_server else 'back_to_menu'),
|
| 236 |
+
InlineKeyboardButton(f"👤 Имя: {username}", callback_data='set_username'),
|
| 237 |
+
]
|
| 238 |
+
keyboard.append(settings_buttons)
|
| 239 |
+
|
| 240 |
+
core_buttons = [
|
| 241 |
+
InlineKeyboardButton(f"📦 Ядро: {server_type.upper()}",
|
| 242 |
+
callback_data='set_server_type' if not is_setting_type else 'back_to_menu'),
|
| 243 |
+
InlineKeyboardButton(f"🕹️ Версия: {version}",
|
| 244 |
+
callback_data='set_version' if not is_setting_version else 'back_to_menu'),
|
| 245 |
+
]
|
| 246 |
+
keyboard.append(core_buttons)
|
| 247 |
+
|
| 248 |
+
keyboard.append([InlineKeyboardButton(start_stop_text, callback_data='toggle_bot')])
|
| 249 |
+
|
| 250 |
+
ai_buttons = [
|
| 251 |
+
InlineKeyboardButton("🤖 AI: !start", callback_data='ai_start'),
|
| 252 |
+
InlineKeyboardButton("⚔️ AI: !stop", callback_data='ai_stop')
|
| 253 |
+
]
|
| 254 |
+
keyboard.append(ai_buttons)
|
| 255 |
+
|
| 256 |
+
if is_setting_server or is_setting_version or is_setting_type:
|
| 257 |
+
keyboard.append([InlineKeyboardButton("🔙 Назад в меню", callback_data='back_to_menu')])
|
| 258 |
+
|
| 259 |
+
return InlineKeyboardMarkup(keyboard)
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
| 263 |
+
chat_id = str(update.effective_chat.id)
|
| 264 |
+
|
| 265 |
+
if chat_id not in USER_DATA:
|
| 266 |
+
USER_DATA[chat_id] = {
|
| 267 |
+
"host": "localhost",
|
| 268 |
+
"port": "25565",
|
| 269 |
+
"username": f"MineflayerBot_{chat_id[:4]}",
|
| 270 |
+
"version": "1.20.1",
|
| 271 |
+
"server_type": "vanilla",
|
| 272 |
+
"state": "menu"
|
| 273 |
+
}
|
| 274 |
+
save_data()
|
| 275 |
+
|
| 276 |
+
is_running = await get_bot_status(chat_id)
|
| 277 |
+
current_state = USER_DATA[chat_id]['state']
|
| 278 |
+
is_setting_server = current_state == 'setting_host_port'
|
| 279 |
+
is_setting_version = current_state == 'setting_version'
|
| 280 |
+
is_setting_type = current_state == 'setting_server_type'
|
| 281 |
+
|
| 282 |
+
reply_markup = get_main_menu_keyboard(chat_id, is_running, is_setting_server, is_setting_version, is_setting_type)
|
| 283 |
+
|
| 284 |
+
data = USER_DATA[chat_id]
|
| 285 |
+
|
| 286 |
+
host_esc = escape_markdown_v2(data['host'], ignore_in_code_block=True)
|
| 287 |
+
port_esc = escape_markdown_v2(data['port'], ignore_in_code_block=True)
|
| 288 |
+
username_esc = escape_markdown_v2(data['username'], ignore_in_code_block=True)
|
| 289 |
+
version_esc = escape_markdown_v2(data['version'], ignore_in_code_block=True)
|
| 290 |
+
server_type_esc = escape_markdown_v2(data['server_type'].upper(), ignore_in_code_block=True)
|
| 291 |
+
|
| 292 |
+
message = (
|
| 293 |
+
"⚙️ **Главное меню управления Mineflayer Bot**\n\n"
|
| 294 |
+
f"🔗 **Сервер:** `{host_esc}:{port_esc}`\n"
|
| 295 |
+
f"👤 **Имя:** `{username_esc}`\n"
|
| 296 |
+
f"📦 **Ядро:** `{server_type_esc}`\n"
|
| 297 |
+
f"🕹️ **Версия:** `{version_esc}`\n"
|
| 298 |
+
f"🚦 **Статус:** {'🟢 Активен' if is_running else '🔴 Неактивен'}\n\n"
|
| 299 |
+
f"_Автоматический Anti\\-Kick \\(спам в чат\\) включен\\._"
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
if update.callback_query is None:
|
| 303 |
+
await update.message.reply_text(message, reply_markup=reply_markup, parse_mode='MarkdownV2')
|
| 304 |
+
else:
|
| 305 |
+
try:
|
| 306 |
+
await update.callback_query.edit_message_text(message, reply_markup=reply_markup, parse_mode='MarkdownV2')
|
| 307 |
+
except Exception:
|
| 308 |
+
pass
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
async def set_server_type_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
| 312 |
+
chat_id = str(update.effective_chat.id)
|
| 313 |
+
USER_DATA[chat_id]['state'] = 'setting_server_type'
|
| 314 |
+
save_data()
|
| 315 |
+
|
| 316 |
+
keyboard = [
|
| 317 |
+
[InlineKeyboardButton("Ванильный (Vanilla)", callback_data='type_vanilla')],
|
| 318 |
+
[InlineKeyboardButton("Paper/Spigot (Плагины)", callback_data='type_spigot')],
|
| 319 |
+
[InlineKeyboardButton("Forge (Моды)", callback_data='type_forge')],
|
| 320 |
+
[InlineKeyboardButton("Fabric (Моды)", callback_data='type_fabric')],
|
| 321 |
+
[InlineKeyboardButton("🔙 Назад в меню", callback_data='back_to_menu')]
|
| 322 |
+
]
|
| 323 |
+
reply_markup = InlineKeyboardMarkup(keyboard)
|
| 324 |
+
|
| 325 |
+
await update.callback_query.edit_message_text(
|
| 326 |
+
"📦 **Выберите тип ядра сервера:**\n\n"
|
| 327 |
+
"*\\(Это важно для правильного подключения к серверам с модами или плагинами\\.\\)*",
|
| 328 |
+
reply_markup=reply_markup,
|
| 329 |
+
parse_mode='MarkdownV2'
|
| 330 |
+
)
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
async def setserver_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
| 334 |
+
chat_id = str(update.effective_chat.id)
|
| 335 |
+
USER_DATA[chat_id]['state'] = 'setting_host_port'
|
| 336 |
+
save_data()
|
| 337 |
+
|
| 338 |
+
message_text = (
|
| 339 |
+
"🔗 **Введите адрес сервера \\(host:port\\)**:\n\n"
|
| 340 |
+
"Пример: `my_server\\.aternos\\.me:25565`\n\n"
|
| 341 |
+
"*\\(Вы можете просто отправить новый адрес в следующем сообщении\\.\\)*"
|
| 342 |
+
)
|
| 343 |
+
|
| 344 |
+
if update.callback_query:
|
| 345 |
+
await update.callback_query.edit_message_text(message_text, parse_mode='MarkdownV2')
|
| 346 |
+
else:
|
| 347 |
+
await update.message.reply_text(message_text, parse_mode='MarkdownV2')
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
async def setversion_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
| 351 |
+
chat_id = str(update.effective_chat.id)
|
| 352 |
+
USER_DATA[chat_id]['state'] = 'setting_version'
|
| 353 |
+
save_data()
|
| 354 |
+
|
| 355 |
+
keyboard = [
|
| 356 |
+
[InlineKeyboardButton("1.20.1", callback_data='v_1.20.1'),
|
| 357 |
+
InlineKeyboardButton("1.19.4", callback_data='v_1.19.4')],
|
| 358 |
+
[InlineKeyboardButton("1.18.2", callback_data='v_1.18.2'),
|
| 359 |
+
InlineKeyboardButton("1.17.1", callback_data='v_1.17.1')],
|
| 360 |
+
[InlineKeyboardButton("1.16.5", callback_data='v_1.16.5'),
|
| 361 |
+
InlineKeyboardButton("1.12.2", callback_data='v_1.12.2')],
|
| 362 |
+
[InlineKeyboardButton("🔙 Назад в меню", callback_data='back_to_menu')]
|
| 363 |
+
]
|
| 364 |
+
reply_markup = InlineKeyboardMarkup(keyboard)
|
| 365 |
+
|
| 366 |
+
await update.callback_query.edit_message_text(
|
| 367 |
+
"🕹️ **Выберите версию Minecraft**:\n\n*\\(Это обязательно должно соответствовать версии сервера\\.\\)*",
|
| 368 |
+
reply_markup=reply_markup,
|
| 369 |
+
parse_mode='MarkdownV2'
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
async def set_username_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
| 374 |
+
chat_id = str(update.effective_chat.id)
|
| 375 |
+
USER_DATA[chat_id]['state'] = 'setting_username'
|
| 376 |
+
save_data()
|
| 377 |
+
|
| 378 |
+
await update.callback_query.edit_message_text(
|
| 379 |
+
"👤 **Введите новое имя для бота:**\n\nПример: `MyFarmingBot`\n\n*\\(Имя должно соответствовать правилам Minecraft\\.\\)*",
|
| 380 |
+
parse_mode='MarkdownV2'
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
| 385 |
+
query = update.callback_query
|
| 386 |
+
await query.answer()
|
| 387 |
+
chat_id = str(query.message.chat.id)
|
| 388 |
+
data = USER_DATA.get(chat_id, {})
|
| 389 |
+
callback_data = query.data
|
| 390 |
+
|
| 391 |
+
if callback_data == 'back_to_menu':
|
| 392 |
+
USER_DATA[chat_id]['state'] = 'menu'
|
| 393 |
+
save_data()
|
| 394 |
+
await start_command(update, context)
|
| 395 |
+
return
|
| 396 |
+
|
| 397 |
+
if callback_data == 'set_server':
|
| 398 |
+
await setserver_command(update, context)
|
| 399 |
+
return
|
| 400 |
+
if callback_data == 'set_version':
|
| 401 |
+
await setversion_command(update, context)
|
| 402 |
+
return
|
| 403 |
+
if callback_data == 'set_username':
|
| 404 |
+
await set_username_command(update, context)
|
| 405 |
+
return
|
| 406 |
+
if callback_data == 'set_server_type':
|
| 407 |
+
await set_server_type_command(update, context)
|
| 408 |
+
return
|
| 409 |
+
|
| 410 |
+
if callback_data.startswith('type_'):
|
| 411 |
+
server_type = callback_data.split('_')[1]
|
| 412 |
+
if server_type == 'spigot':
|
| 413 |
+
server_type = 'vanilla'
|
| 414 |
+
USER_DATA[chat_id]['server_type'] = server_type
|
| 415 |
+
USER_DATA[chat_id]['state'] = 'menu'
|
| 416 |
+
save_data()
|
| 417 |
+
await start_command(update, context)
|
| 418 |
+
return
|
| 419 |
+
|
| 420 |
+
if callback_data.startswith('v_'):
|
| 421 |
+
version = callback_data.split('_')[1]
|
| 422 |
+
USER_DATA[chat_id]['version'] = version
|
| 423 |
+
USER_DATA[chat_id]['state'] = 'menu'
|
| 424 |
+
save_data()
|
| 425 |
+
await start_command(update, context)
|
| 426 |
+
return
|
| 427 |
+
|
| 428 |
+
if callback_data == 'ai_start':
|
| 429 |
+
if await send_command_to_bot(chat_id, "!start"):
|
| 430 |
+
await query.edit_message_text(
|
| 431 |
+
"🤖 **AI Gunner запущен\\!** Он начнет поиск оружия и мобов\\. *\\(Через несколько секунд бот ответит в чате сервера\\)*",
|
| 432 |
+
parse_mode='MarkdownV2')
|
| 433 |
+
else:
|
| 434 |
+
await query.edit_message_text(
|
| 435 |
+
"❌ **Ошибка:** Не удалось отправить команду \\!start\\. Убедитесь, что бот запущен\\.",
|
| 436 |
+
parse_mode='MarkdownV2')
|
| 437 |
+
return
|
| 438 |
+
|
| 439 |
+
if callback_data == 'ai_stop':
|
| 440 |
+
if await send_command_to_bot(chat_id, "!stop"):
|
| 441 |
+
await query.edit_message_text("⚔️ **AI Gunner остановлен\\!** Бот переходит в режим ожидания\\.",
|
| 442 |
+
parse_mode='MarkdownV2')
|
| 443 |
+
else:
|
| 444 |
+
await query.edit_message_text(
|
| 445 |
+
"❌ **Ошибка:** Не удалось отправить команду \\!stop\\. Убедитесь, что бот запущен\\.",
|
| 446 |
+
parse_mode='MarkdownV2')
|
| 447 |
+
return
|
| 448 |
+
|
| 449 |
+
if callback_data == 'toggle_bot':
|
| 450 |
+
is_running = await get_bot_status(chat_id)
|
| 451 |
+
if is_running:
|
| 452 |
+
if await stop_bot_in_worker(chat_id):
|
| 453 |
+
message = "🛑 **Бот остановлен\\!**"
|
| 454 |
+
else:
|
| 455 |
+
message = "❌ **Ошибка:** Не удалось отправить команду остановки Worker Service\\."
|
| 456 |
+
else:
|
| 457 |
+
host = data.get('host')
|
| 458 |
+
port = data.get('port')
|
| 459 |
+
version = data.get('version')
|
| 460 |
+
server_type = data.get('server_type')
|
| 461 |
+
|
| 462 |
+
if not host or host == 'localhost':
|
| 463 |
+
await query.edit_message_text("❌ **Ошибка:** Сначала укажите адрес сервера через '🔗 Сервер'\\.",
|
| 464 |
+
parse_mode='MarkdownV2')
|
| 465 |
+
return
|
| 466 |
+
|
| 467 |
+
if await start_bot_in_worker(chat_id, host, port, data.get('username'), version, server_type):
|
| 468 |
+
host_esc = escape_markdown_v2(host, ignore_in_code_block=True)
|
| 469 |
+
port_esc = escape_markdown_v2(port, ignore_in_code_block=True)
|
| 470 |
+
version_esc = escape_markdown_v2(version, ignore_in_code_block=True)
|
| 471 |
+
server_type_esc = escape_markdown_v2(server_type.upper(), ignore_in_code_block=True)
|
| 472 |
+
message = f"▶️ **Бот запущен\\!** Попытка подключения к `{host_esc}:{port_esc}` \\(Версия: `{version_esc}`, Ядро: `{server_type_esc}`\\)\\. Ожидайте уведомления о статусе\\."
|
| 473 |
+
else:
|
| 474 |
+
message = "❌ **Ошибка:** Не удалось отправить команду запуска Worker Service\\. Проверьте логи или статус Worker'а\\."
|
| 475 |
+
|
| 476 |
+
USER_DATA[chat_id]['state'] = 'menu'
|
| 477 |
+
save_data()
|
| 478 |
+
await query.message.reply_text(message, parse_mode='MarkdownV2')
|
| 479 |
+
await start_command(update, context)
|
| 480 |
+
return
|
| 481 |
+
|
| 482 |
+
if callback_data == 'status':
|
| 483 |
+
await start_command(update, context)
|
| 484 |
+
return
|
| 485 |
+
|
| 486 |
+
|
| 487 |
+
async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
| 488 |
+
chat_id = str(update.effective_chat.id)
|
| 489 |
+
user_input = update.message.text.strip()
|
| 490 |
+
data = USER_DATA.get(chat_id)
|
| 491 |
+
|
| 492 |
+
if not data:
|
| 493 |
+
await start_command(update, context)
|
| 494 |
+
return
|
| 495 |
+
|
| 496 |
+
state = data.get('state', 'menu')
|
| 497 |
+
|
| 498 |
+
if state == 'setting_host_port':
|
| 499 |
+
if ':' in user_input:
|
| 500 |
+
parts = user_input.split(':')
|
| 501 |
+
host = parts[0]
|
| 502 |
+
port = parts[1] if len(parts) > 1 else "25565"
|
| 503 |
+
if not port.isdigit():
|
| 504 |
+
await update.message.reply_text("❌ **Ошибка:** Порт должен быть числом\\.", parse_mode='MarkdownV2')
|
| 505 |
+
return
|
| 506 |
+
USER_DATA[chat_id]['host'] = host
|
| 507 |
+
USER_DATA[chat_id]['port'] = port
|
| 508 |
+
USER_DATA[chat_id]['state'] = 'menu'
|
| 509 |
+
save_data()
|
| 510 |
+
host_esc = escape_markdown_v2(host, ignore_in_code_block=True)
|
| 511 |
+
port_esc = escape_markdown_v2(port, ignore_in_code_block=True)
|
| 512 |
+
await update.message.reply_text(f"✅ Сервер установлен: `{host_esc}:{port_esc}`", parse_mode='MarkdownV2')
|
| 513 |
+
await start_command(update, context)
|
| 514 |
+
else:
|
| 515 |
+
await update.message.reply_text("❌ **Ошибка:** Введите в формате `host:port`\\.", parse_mode='MarkdownV2')
|
| 516 |
+
|
| 517 |
+
elif state == 'setting_username':
|
| 518 |
+
if 3 <= len(user_input) <= 16 and all(c.isalnum() or c in '_-' for c in user_input):
|
| 519 |
+
USER_DATA[chat_id]['username'] = user_input
|
| 520 |
+
USER_DATA[chat_id]['state'] = 'menu'
|
| 521 |
+
save_data()
|
| 522 |
+
username_esc = escape_markdown_v2(user_input, ignore_in_code_block=True)
|
| 523 |
+
await update.message.reply_text(f"✅ Имя установлено: `{username_esc}`", parse_mode='MarkdownV2')
|
| 524 |
+
await start_command(update, context)
|
| 525 |
+
else:
|
| 526 |
+
await update.message.reply_text(
|
| 527 |
+
"❌ **Ошибка:** Имя должно быть от 3 до 16 символов и содержать только латинские буквы, цифры, \\_ или \\-\\.",
|
| 528 |
+
parse_mode='MarkdownV2')
|
| 529 |
+
|
| 530 |
+
elif state == 'setting_version':
|
| 531 |
+
await update.message.reply_text(
|
| 532 |
+
"⚠️ **Используйте кнопки для выбора версии\\.** Нажмите /menu и выберите '🕹️ Версия'\\.",
|
| 533 |
+
parse_mode='MarkdownV2')
|
| 534 |
+
|
| 535 |
+
elif state == 'setting_server_type':
|
| 536 |
+
await update.message.reply_text(
|
| 537 |
+
"⚠️ **Используйте кнопки для выбора типа ядра\\.** Нажмите /menu и выберите '📦 Ядро'\\.",
|
| 538 |
+
parse_mode='MarkdownV2')
|
| 539 |
+
|
| 540 |
+
elif state == 'menu':
|
| 541 |
+
is_running = await get_bot_status(chat_id)
|
| 542 |
+
if is_running:
|
| 543 |
+
if await send_command_to_bot(chat_id, user_input):
|
| 544 |
+
user_input_esc = escape_markdown_v2(user_input, ignore_in_code_block=True)
|
| 545 |
+
await update.message.reply_text(f"💬 Команда отправлена боту: `{user_input_esc}`",
|
| 546 |
+
parse_mode='MarkdownV2')
|
| 547 |
+
else:
|
| 548 |
+
await update.message.reply_text(
|
| 549 |
+
"❌ **Ошибка:** Не удалось отправить команду Worker Service\\. Проверьте логи Worker'а\\.",
|
| 550 |
+
parse_mode='MarkdownV2')
|
| 551 |
+
else:
|
| 552 |
+
await update.message.reply_text("🤖 Бот не запущен\\. Запустите его через /menu\\.", parse_mode='MarkdownV2')
|
| 553 |
+
|
| 554 |
+
|
| 555 |
+
# ----------------------------------------------------------------------
|
| 556 |
+
# ТОЧКА ВХОДА (POLLING)
|
| 557 |
+
# ----------------------------------------------------------------------
|
| 558 |
+
|
| 559 |
+
def main():
|
| 560 |
+
global tg_app
|
| 561 |
+
|
| 562 |
+
load_data()
|
| 563 |
+
|
| 564 |
+
tg_app = Application.builder().token(TELEGRAM_TOKEN).build()
|
| 565 |
+
|
| 566 |
+
# Регистрация команд и обработчиков
|
| 567 |
+
tg_app.add_handler(CommandHandler(["start", "menu"], start_command))
|
| 568 |
+
tg_app.add_handler(CallbackQueryHandler(button_callback))
|
| 569 |
+
tg_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, text_message_handler))
|
| 570 |
+
|
| 571 |
+
# --- ⚠️ РЕГИСТРАЦИЯ ANTI-AFK ЗАДАЧИ ---
|
| 572 |
+
# Запускается каждые 45 секунд, первый запуск через 10 секунд
|
| 573 |
+
tg_app.job_queue.run_repeating(anti_afk_job, interval=45, first=10)
|
| 574 |
+
# --------------------------------------
|
| 575 |
+
|
| 576 |
+
logger.info("Бот запущен...")
|
| 577 |
+
|
| 578 |
+
tg_app.run_polling(drop_pending_updates=True)
|
| 579 |
+
|
| 580 |
+
|
| 581 |
+
if __name__ == '__main__':
|
| 582 |
+
main()
|