Sk0lovek commited on
Commit
e1d1022
·
verified ·
1 Parent(s): 1683bca

Upload bot.py

Browse files
Files changed (1) hide show
  1. bot.py +582 -0
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()