Иконка ресурса

Telegram bot - мониторинг сервера CSS 1.1

YungWasty

Участник
Сообщения
5
Реакции
5
  • Автор ресурса
  • #1
YungWasty добавил(а) новый ресурс:

Telegram bot - мониторинг сервера CSS - Telegram-бот для мониторинга игровых серверов Counter-Strike: Source

🤖 CS:Source Server Monitor Bot — Telegram-бот для мониторинга игровых серверов Counter-Strike: Source v34.

Основные функции:

  • 📡 Автоматическая проверка статуса: Отслеживает онлайн игроков, текущую карту и список игроков.
  • 🔄 Автообновления: Публикует изменения в Telegram-канале каждые 5 минут (в коде можно изменить, строка 26 - UPDATE_INTERVAL).
  • 🎮 Ручные запросы: По команде !сервер мгновенно выводит актуальную информацию.
  • 💬 Корректная...

Узнать больше об этом ресурсе...
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,519
Реакции
4,979
Хм, я лет пять назад написал себе модуль к боту на питоне, который позволял создавать для каждого акка телеги свой список серверов и по команде выводить инфу о них. Показывало название сервера, карту, текущий онлайн. Даже сделал чтобы выводилось блоками по десять серверов в одном сообщении. Инфа принудительно выводилось в личку, чтобы общий чат не засирался.
Правда нужно было ещё реализовать удаление серверов из списка через команду, но я просто забил на это (к тому времени я и бот тот использовать перестал). Хотел сделать выводом кнопок для выбора через них какой сервер удалять, но не вкурил как создавать клавиатуру в том боте.
Ну а добавлялись сервера в список с помощью команды (и вроде даже немного валидность проверялась). Список в текстовый файл json сохранялся, емнип.
Для запросов к серверу использовалась библиотека valve.source.a2s.
Ещё бы вспомнить где на винтах файлы бота остались. 😒
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,519
Реакции
4,979
@zAntibot, так это не плагин SM. По идее должен связываться с сервером любой игры на движке Source.
У меня к 3 разным играм, помимо CS:S, без проблем запросы слал и получал ответы. Этот по идее тоже должен так уметь.
 

zAntibot

Участник
Сообщения
126
Реакции
45
@zAntibot, так это не плагин SM. По идее должен связываться с сервером любой игры на движке Source.
У меня к 3 разным играм, помимо CS:S, без проблем запросы слал и получал ответы. Этот по идее тоже должен так уметь.
просто автор указал что именно 34 вот и решил уточнить, вдруг есть нюансы что с v93 не работает.
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,519
Реакции
4,979

zAntibot

Участник
Сообщения
126
Реакции
45
Может кто то доработать бота что бы он постил в определенную тему в группе THREAD_ID у меня не хватает знаний что бы его переписать по python-telegram-bot 20+ где есть THREAD_ID. Спасибо
 

MODER DEV

Участник
Сообщения
8
Реакции
4
Может кто то доработать бота что бы он постил в определенную тему в группе THREAD_ID у меня не хватает знаний что бы его переписать по python-telegram-bot 20+ где есть THREAD_ID. Спасибо
Код:
import os
import re
import logging
import socket
import struct
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackContext, CallbackQueryHandler
from dotenv import load_dotenv

# Настройка логирования
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO,
    handlers=[
        logging.FileHandler('server_bot.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

load_dotenv()
TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
CHANNEL_ID = os.getenv('TELEGRAM_CHANNEL_ID')
SERVER_IP = 'Ваш ип' #Сюда вписать ип адрес пример: 00.00.00.00
SERVER_PORT = 27015 #Сюда вписать ваш порт, пример 27015
UPDATE_INTERVAL = 3000 #интервал автоотправки сообщения в ваш канал
THREAD_ID = 123456789  # ID темы в группе, куда будут отправляться посты

class SourceServerQuery:
    last_response = None
    ENCODINGS = ['utf-8', 'cp1251', 'iso-8859-5', 'cp866', 'koi8-r', 'latin1']
    HEADER = b'\xFF\xFF\xFF\xFF'

    @staticmethod
    def remove_color_codes(name):
        return re.sub(r'\^\d', '', name).strip() if name else ''

    @staticmethod
    def decode_string(data):
        end = data.find(b'\x00')
        if end == -1:
            return "", data
        raw_bytes = data[:end]
        remaining = data[end+1:]
        for encoding in SourceServerQuery.ENCODINGS:
            try:
                decoded = raw_bytes.decode(encoding, errors='strict').strip()
                return decoded, remaining
            except UnicodeDecodeError:
                continue
        return raw_bytes.decode('utf-8', errors='replace').strip(), remaining

    @staticmethod
    def get_info():
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
                sock.settimeout(5)
                payload = SourceServerQuery.HEADER + b'T' + b'Source Engine Query\x00'
                sock.sendto(payload, (SERVER_IP, SERVER_PORT))
                data = sock.recv(4096)

                if data[4] == 0x41:
                    challenge = struct.unpack('<l', data[5:9])[0]
                    payload = SourceServerQuery.HEADER + b'T' + b'Source Engine Query\x00' + struct.pack('<l', challenge)
                    sock.sendto(payload, (SERVER_IP, SERVER_PORT))
                    data = sock.recv(4096)

                if data[4] != 0x49:
                    return None

                data = data[6:]
                info = {}
                info['name'], data = SourceServerQuery.decode_string(data)
                info['map'], data = SourceServerQuery.decode_string(data)
                data = data[16:]
                info['version'], data = SourceServerQuery.decode_string(data)
               
                return {
                    'name': SourceServerQuery.remove_color_codes(info['name']),
                    'map': SourceServerQuery.remove_color_codes(info['map']),
                    'players': data[0] if len(data) >= 2 else 0,
                    'max_players': data[1] if len(data) >= 2 else 0
                }

        except Exception as e:
            logger.error(f"Ошибка запроса информации: {str(e)}")
            return None

    @staticmethod
    def get_players():
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
                sock.settimeout(5)
                payload = SourceServerQuery.HEADER + b'U' + b'\xFF\xFF\xFF\xFF'
                sock.sendto(payload, (SERVER_IP, SERVER_PORT))
                data = sock.recv(4096)

                if data[4] == 0x41:
                    challenge = struct.unpack('<l', data[5:9])[0]
                    payload = SourceServerQuery.HEADER + b'U' + struct.pack('<l', challenge)
                    sock.sendto(payload, (SERVER_IP, SERVER_PORT))
                    data = sock.recv(4096)

                if data[4] != 0x44:
                    return None

                players = []
                player_count = data[5]
                data = data[6:]

                for _ in range(player_count):
                    try:
                        data = data[1:]
                        name, data = SourceServerQuery.decode_string(data)
                        data = data[8:]
                        clean_name = SourceServerQuery.remove_color_codes(name)
                        if clean_name and clean_name != '.':
                            players.append(clean_name)
                    except Exception as e:
                        logger.error(f"Ошибка парсинга игрока: {str(e)}")
                        continue

                return players

        except Exception as e:
            logger.error(f"Ошибка запроса игроков: {str(e)}")
            return None

def generate_message(check_changes=True):
    try:
        current_data = (SourceServerQuery.get_info(), SourceServerQuery.get_players())
       
        if check_changes and current_data == SourceServerQuery.last_response:
            return None

        SourceServerQuery.last_response = current_data
        info, players = current_data

        if not info or not players:
            return "❌ Сервер не отвечает"

        message = [
            f"🔹 <b>{info['name']}</b>",
            f"🗺 Карта: <code>{info['map']}</code>",
            f"👥 Онлайн: <b>{len(players)}/32</b>",
            "\n📊 Игроки:"
        ]

        if players:
            message += [f"👤 {name}" for name in players]
        else:
            message.append("Сейчас никто не играет")

        return "\n".join(message)

    except Exception as e:
        logger.error(f"Ошибка генерации сообщения: {str(e)}")
        return "⚠️ Ошибка получения данных"

def send_update(context: CallbackContext):
    try:
        message = generate_message(check_changes=True)
        if message:
            # Отправка в канал
            context.bot.send_message(
                chat_id=CHANNEL_ID,
                text=message,
                parse_mode='HTML'
            )
           
            # Отправка в тему группы
            context.bot.send_message(
                chat_id=CHANNEL_ID,
                text=message,
                parse_mode='HTML',
                message_thread_id=THREAD_ID
            )
    except Exception as e:
        logger.error(f"Ошибка отправки: {str(e)}")

def send_to_thread(update: Update, context: CallbackContext):
    """Команда для отправки сообщения в тему группы"""
    try:
        message = generate_message(check_changes=False)
        if message:
            context.bot.send_message(
                chat_id=CHANNEL_ID,
                text=message,
                parse_mode='HTML',
                message_thread_id=THREAD_ID
            )
            update.message.reply_text("✅ Сообщение отправлено в тему группы", parse_mode='HTML')
        else:
            update.message.reply_text("❌ Не удалось получить данные сервера", parse_mode='HTML')
    except Exception as e:
        logger.error(f"Ошибка отправки в тему: {str(e)}")
        update.message.reply_text("⚠️ Ошибка при отправке в тему", parse_mode='HTML')

def handle_server_cmd(update: Update, context: CallbackContext):
    try:
        info = SourceServerQuery.get_info()
        players = SourceServerQuery.get_players()
       
        if not info or not players:
            update.message.reply_text("❌ Сервер не отвечает", parse_mode='HTML')
            return

        message = [
            f"🔹 <b>{info['name']}</b>",
            f"🗺 Карта: <code>{info['map']}</code>",
            f"👥 Онлайн: <b>{len(players)}/32</b>",
            "\n📊 Игроки:"
        ]

        if players:
            message += [f"👤 {name}" for name in players]
        else:
            message.append("Сейчас никто не играет")

        update.message.reply_text(
            text="\n".join(message),
            parse_mode='HTML'
        )

    except Exception as e:
        logger.error(f"Ошибка команды: {str(e)}")
        update.message.reply_text("⚠️ Ошибка при получении данных", parse_mode='HTML')

def start(update: Update, context: CallbackContext):
    update.message.reply_text(
        "🤖 Бот мониторинга игрового сервера\n"
        "Используйте команду !сервер для проверки текущего статуса\n"
        "Используйте команду /send_to_thread для отправки в тему группы",
        parse_mode='HTML'
    )

def main():
    try:
        updater = Updater(TOKEN, use_context=True)
        dp = updater.dispatcher

        dp.add_handler(CommandHandler("start", start))
        dp.add_handler(CommandHandler("send_to_thread", send_to_thread))
        dp.add_handler(MessageHandler(
            Filters.text & ~Filters.command & Filters.regex(r'^!сервер'),
            handle_server_cmd
        ))

        updater.job_queue.run_repeating(
            send_update,
            interval=UPDATE_INTERVAL,
            first=0
        )

        logger.info("Бот успешно запущен")
        updater.start_polling()
        updater.idle()

    except Exception as e:
        logger.critical(f"Критическая ошибка: {str(e)}")
        raise

if __name__ == '__main__':
    main()


Добавлена новая команда /send_to_thread - позволяет вручную отправлять сообщения в тему группы.

  1. Установи правильный THREAD_ID - это ID темы в группе
  2. Используй команду /send_to_thread для ручной отправки в тему
  3. Автоматические обновления будут отправляться в оба места (канал и тему)
 

zAntibot

Участник
Сообщения
126
Реакции
45
Код:
import os
import re
import logging
import socket
import struct
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackContext, CallbackQueryHandler
from dotenv import load_dotenv

# Настройка логирования
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO,
    handlers=[
        logging.FileHandler('server_bot.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

load_dotenv()
TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
CHANNEL_ID = os.getenv('TELEGRAM_CHANNEL_ID')
SERVER_IP = 'Ваш ип' #Сюда вписать ип адрес пример: 00.00.00.00
SERVER_PORT = 27015 #Сюда вписать ваш порт, пример 27015
UPDATE_INTERVAL = 3000 #интервал автоотправки сообщения в ваш канал
THREAD_ID = 123456789  # ID темы в группе, куда будут отправляться посты

class SourceServerQuery:
    last_response = None
    ENCODINGS = ['utf-8', 'cp1251', 'iso-8859-5', 'cp866', 'koi8-r', 'latin1']
    HEADER = b'\xFF\xFF\xFF\xFF'

    @staticmethod
    def remove_color_codes(name):
        return re.sub(r'\^\d', '', name).strip() if name else ''

    @staticmethod
    def decode_string(data):
        end = data.find(b'\x00')
        if end == -1:
            return "", data
        raw_bytes = data[:end]
        remaining = data[end+1:]
        for encoding in SourceServerQuery.ENCODINGS:
            try:
                decoded = raw_bytes.decode(encoding, errors='strict').strip()
                return decoded, remaining
            except UnicodeDecodeError:
                continue
        return raw_bytes.decode('utf-8', errors='replace').strip(), remaining

    @staticmethod
    def get_info():
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
                sock.settimeout(5)
                payload = SourceServerQuery.HEADER + b'T' + b'Source Engine Query\x00'
                sock.sendto(payload, (SERVER_IP, SERVER_PORT))
                data = sock.recv(4096)

                if data[4] == 0x41:
                    challenge = struct.unpack('<l', data[5:9])[0]
                    payload = SourceServerQuery.HEADER + b'T' + b'Source Engine Query\x00' + struct.pack('<l', challenge)
                    sock.sendto(payload, (SERVER_IP, SERVER_PORT))
                    data = sock.recv(4096)

                if data[4] != 0x49:
                    return None

                data = data[6:]
                info = {}
                info['name'], data = SourceServerQuery.decode_string(data)
                info['map'], data = SourceServerQuery.decode_string(data)
                data = data[16:]
                info['version'], data = SourceServerQuery.decode_string(data)
              
                return {
                    'name': SourceServerQuery.remove_color_codes(info['name']),
                    'map': SourceServerQuery.remove_color_codes(info['map']),
                    'players': data[0] if len(data) >= 2 else 0,
                    'max_players': data[1] if len(data) >= 2 else 0
                }

        except Exception as e:
            logger.error(f"Ошибка запроса информации: {str(e)}")
            return None

    @staticmethod
    def get_players():
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
                sock.settimeout(5)
                payload = SourceServerQuery.HEADER + b'U' + b'\xFF\xFF\xFF\xFF'
                sock.sendto(payload, (SERVER_IP, SERVER_PORT))
                data = sock.recv(4096)

                if data[4] == 0x41:
                    challenge = struct.unpack('<l', data[5:9])[0]
                    payload = SourceServerQuery.HEADER + b'U' + struct.pack('<l', challenge)
                    sock.sendto(payload, (SERVER_IP, SERVER_PORT))
                    data = sock.recv(4096)

                if data[4] != 0x44:
                    return None

                players = []
                player_count = data[5]
                data = data[6:]

                for _ in range(player_count):
                    try:
                        data = data[1:]
                        name, data = SourceServerQuery.decode_string(data)
                        data = data[8:]
                        clean_name = SourceServerQuery.remove_color_codes(name)
                        if clean_name and clean_name != '.':
                            players.append(clean_name)
                    except Exception as e:
                        logger.error(f"Ошибка парсинга игрока: {str(e)}")
                        continue

                return players

        except Exception as e:
            logger.error(f"Ошибка запроса игроков: {str(e)}")
            return None

def generate_message(check_changes=True):
    try:
        current_data = (SourceServerQuery.get_info(), SourceServerQuery.get_players())
      
        if check_changes and current_data == SourceServerQuery.last_response:
            return None

        SourceServerQuery.last_response = current_data
        info, players = current_data

        if not info or not players:
            return "❌ Сервер не отвечает"

        message = [
            f"🔹 <b>{info['name']}</b>",
            f"🗺 Карта: <code>{info['map']}</code>",
            f"👥 Онлайн: <b>{len(players)}/32</b>",
            "\n📊 Игроки:"
        ]

        if players:
            message += [f"👤 {name}" for name in players]
        else:
            message.append("Сейчас никто не играет")

        return "\n".join(message)

    except Exception as e:
        logger.error(f"Ошибка генерации сообщения: {str(e)}")
        return "⚠️ Ошибка получения данных"

def send_update(context: CallbackContext):
    try:
        message = generate_message(check_changes=True)
        if message:
            # Отправка в канал
            context.bot.send_message(
                chat_id=CHANNEL_ID,
                text=message,
                parse_mode='HTML'
            )
          
            # Отправка в тему группы
            context.bot.send_message(
                chat_id=CHANNEL_ID,
                text=message,
                parse_mode='HTML',
                message_thread_id=THREAD_ID
            )
    except Exception as e:
        logger.error(f"Ошибка отправки: {str(e)}")

def send_to_thread(update: Update, context: CallbackContext):
    """Команда для отправки сообщения в тему группы"""
    try:
        message = generate_message(check_changes=False)
        if message:
            context.bot.send_message(
                chat_id=CHANNEL_ID,
                text=message,
                parse_mode='HTML',
                message_thread_id=THREAD_ID
            )
            update.message.reply_text("✅ Сообщение отправлено в тему группы", parse_mode='HTML')
        else:
            update.message.reply_text("❌ Не удалось получить данные сервера", parse_mode='HTML')
    except Exception as e:
        logger.error(f"Ошибка отправки в тему: {str(e)}")
        update.message.reply_text("⚠️ Ошибка при отправке в тему", parse_mode='HTML')

def handle_server_cmd(update: Update, context: CallbackContext):
    try:
        info = SourceServerQuery.get_info()
        players = SourceServerQuery.get_players()
      
        if not info or not players:
            update.message.reply_text("❌ Сервер не отвечает", parse_mode='HTML')
            return

        message = [
            f"🔹 <b>{info['name']}</b>",
            f"🗺 Карта: <code>{info['map']}</code>",
            f"👥 Онлайн: <b>{len(players)}/32</b>",
            "\n📊 Игроки:"
        ]

        if players:
            message += [f"👤 {name}" for name in players]
        else:
            message.append("Сейчас никто не играет")

        update.message.reply_text(
            text="\n".join(message),
            parse_mode='HTML'
        )

    except Exception as e:
        logger.error(f"Ошибка команды: {str(e)}")
        update.message.reply_text("⚠️ Ошибка при получении данных", parse_mode='HTML')

def start(update: Update, context: CallbackContext):
    update.message.reply_text(
        "🤖 Бот мониторинга игрового сервера\n"
        "Используйте команду !сервер для проверки текущего статуса\n"
        "Используйте команду /send_to_thread для отправки в тему группы",
        parse_mode='HTML'
    )

def main():
    try:
        updater = Updater(TOKEN, use_context=True)
        dp = updater.dispatcher

        dp.add_handler(CommandHandler("start", start))
        dp.add_handler(CommandHandler("send_to_thread", send_to_thread))
        dp.add_handler(MessageHandler(
            Filters.text & ~Filters.command & Filters.regex(r'^!сервер'),
            handle_server_cmd
        ))

        updater.job_queue.run_repeating(
            send_update,
            interval=UPDATE_INTERVAL,
            first=0
        )

        logger.info("Бот успешно запущен")
        updater.start_polling()
        updater.idle()

    except Exception as e:
        logger.critical(f"Критическая ошибка: {str(e)}")
        raise

if __name__ == '__main__':
    main()


Добавлена новая команда /send_to_thread - позволяет вручную отправлять сообщения в тему группы.

  1. Установи правильный THREAD_ID - это ID темы в группе
  2. Используй команду /send_to_thread для ручной отправки в тему
  3. Автоматические обновления будут отправляться в оба места (канал и тему)
Отправил в основный чат, а в нужную тему нет. Мне надо что бы он не засорял основной чат и постил только в определенную тему.

C-подобный:
/lib/python3.11/site-packages/telegram/utils/request.py", line 283, in _request_wrapper
    raise Conflict(message)
telegram.error.Conflict: Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
 

MODER DEV

Участник
Сообщения
8
Реакции
4
/lib/python3.11/site-packages/telegram/utils/request.py", line 283, in _request_wrapper raise Conflict(message) telegram.error.Conflict: Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
Эта ошибка означает, что у тебя запущено несколько экземпляров бота одновременно
Сообщения автоматически склеены:

Мне надо что бы он не засорял основной чат и постил только в определенную тему.
замени:

Этот код::
def send_update(context: CallbackContext):
    try:
        message = generate_message(check_changes=True)
        if message:
            # Отправка в канал
            context.bot.send_message(
                chat_id=CHANNEL_ID,
                text=message,
                parse_mode='HTML'
            )
          
            # Отправка в тему группы
            context.bot.send_message(
                chat_id=CHANNEL_ID,
                text=message,
                parse_mode='HTML',
                message_thread_id=THREAD_ID
            )
    except Exception as e:
        logger.error(f"Ошибка отправки: {str(e)}")

На этот:


Новый:
def send_update(context: CallbackContext):
    try:
        message = generate_message(check_changes=True)
        if message:
            # Отправка только в тему группы
            context.bot.send_message(
                chat_id=CHANNEL_ID,
                text=message,
                parse_mode='HTML',
                message_thread_id=THREAD_ID
            )
    except Exception as e:
        logger.error(f"Ошибка отправки: {str(e)}")


И будет отправлять только в тему
 

zAntibot

Участник
Сообщения
126
Реакции
45
Спасибо, а вот эту ошибку как исправить 2025-08-11 05:56:57,395 - __main__ - ERROR - Ошибка отправки: Bot.send_message() got an unexpected keyword argument 'message_thread_id'
 

MODER DEV

Участник
Сообщения
8
Реакции
4
Спасибо, а вот эту ошибку как исправить 2025-08-11 05:56:57,395 - __main__ - ERROR - Ошибка отправки: Bot.send_message() got an unexpected keyword argument 'message_thread_id'
python-telegram-bot обнови до последней версии
Сообщения автоматически склеены:

Версия для тех кому нужна отправка только в определённую тему:

pip install pytz==2023.3 apscheduler==3.10.4


C-подобный:
import os
import re
import logging
import socket
import struct
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
    Application, CommandHandler, MessageHandler,
    CallbackContext, CallbackQueryHandler, filters
)
from dotenv import load_dotenv

# Настройка логирования
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO,
    handlers=[
        logging.FileHandler('server_bot.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

load_dotenv()
TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
CHANNEL_ID = os.getenv('TELEGRAM_CHANNEL_ID')
SERVER_IP = 'Ваш ип'  # Сюда вписать ип адрес пример: 00.00.00.00
SERVER_PORT = 27015   # Сюда вписать ваш порт, пример 27015
UPDATE_INTERVAL = 3000  # интервал автоотправки сообщения в ваш канал
THREAD_ID = 123456789  # ID темы в группе, куда будут отправляться посты

class SourceServerQuery:
    last_response = None
    ENCODINGS = ['utf-8', 'cp1251', 'iso-8859-5', 'cp866', 'koi8-r', 'latin1']
    HEADER = b'\xFF\xFF\xFF\xFF'

    @staticmethod
    def remove_color_codes(name):
        return re.sub(r'\^\d', '', name).strip() if name else ''

    @staticmethod
    def decode_string(data):
        end = data.find(b'\x00')
        if end == -1:
            return "", data
        raw_bytes = data[:end]
        remaining = data[end+1:]
        for encoding in SourceServerQuery.ENCODINGS:
            try:
                decoded = raw_bytes.decode(encoding, errors='strict').strip()
                return decoded, remaining
            except UnicodeDecodeError:
                continue
        return raw_bytes.decode('utf-8', errors='replace').strip(), remaining

    @staticmethod
    def get_info():
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
                sock.settimeout(5)
                payload = SourceServerQuery.HEADER + b'T' + b'Source Engine Query\x00'
                sock.sendto(payload, (SERVER_IP, SERVER_PORT))
                data = sock.recv(4096)

                if data[4] == 0x41:
                    challenge = struct.unpack('<l', data[5:9])[0]
                    payload = SourceServerQuery.HEADER + b'T' + b'Source Engine Query\x00' + struct.pack('<l', challenge)
                    sock.sendto(payload, (SERVER_IP, SERVER_PORT))
                    data = sock.recv(4096)

                if data[4] != 0x49:
                    return None

                data = data[6:]
                info = {}
                info['name'], data = SourceServerQuery.decode_string(data)
                info['map'], data = SourceServerQuery.decode_string(data)
                data = data[16:]
                info['version'], data = SourceServerQuery.decode_string(data)
              
                return {
                    'name': SourceServerQuery.remove_color_codes(info['name']),
                    'map': SourceServerQuery.remove_color_codes(info['map']),
                    'players': data[0] if len(data) >= 2 else 0,
                    'max_players': data[1] if len(data) >= 2 else 0
                }

        except Exception as e:
            logger.error(f"Ошибка запроса информации: {str(e)}")
            return None

    @staticmethod
    def get_players():
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
                sock.settimeout(5)
                payload = SourceServerQuery.HEADER + b'U' + b'\xFF\xFF\xFF\xFF'
                sock.sendto(payload, (SERVER_IP, SERVER_PORT))
                data = sock.recv(4096)

                if data[4] == 0x41:
                    challenge = struct.unpack('<l', data[5:9])[0]
                    payload = SourceServerQuery.HEADER + b'U' + struct.pack('<l', challenge)
                    sock.sendto(payload, (SERVER_IP, SERVER_PORT))
                    data = sock.recv(4096)

                if data[4] != 0x44:
                    return None

                players = []
                player_count = data[5]
                data = data[6:]

                for _ in range(player_count):
                    try:
                        data = data[1:]
                        name, data = SourceServerQuery.decode_string(data)
                        data = data[8:]
                        clean_name = SourceServerQuery.remove_color_codes(name)
                        if clean_name and clean_name != '.':
                            players.append(clean_name)
                    except Exception as e:
                        logger.error(f"Ошибка парсинга игрока: {str(e)}")
                        continue

                return players

        except Exception as e:
            logger.error(f"Ошибка запроса игроков: {str(e)}")
            return None

def generate_message(check_changes=True):
    try:
        current_data = (SourceServerQuery.get_info(), SourceServerQuery.get_players())
      
        if check_changes and current_data == SourceServerQuery.last_response:
            return None

        SourceServerQuery.last_response = current_data
        info, players = current_data

        if not info or not players:
            return "❌ Сервер не отвечает"

        message = [
            f"🔹 <b>{info['name']}</b>",
            f"🗺 Карта: <code>{info['map']}</code>",
            f"👥 Онлайн: <b>{len(players)}/32</b>",
            "\n📊 Игроки:"
        ]

        if players:
            message += [f"👤 {name}" for name in players]
        else:
            message.append("Сейчас никто не играет")

        return "\n".join(message)

    except Exception as e:
        logger.error(f"Ошибка генерации сообщения: {str(e)}")
        return "⚠️ Ошибка получения данных"

async def send_update(context: CallbackContext):
    try:
        message = generate_message(check_changes=True)
        if message:
            # Отправка только в тему группы
            await context.bot.send_message(
                chat_id=CHANNEL_ID,
                text=message,
                parse_mode='HTML',
                message_thread_id=THREAD_ID
            )
    except Exception as e:
        logger.error(f"Ошибка отправки: {str(e)}")

async def send_to_thread(update: Update, context: CallbackContext):
    """Команда для отправки сообщения в тему группы"""
    try:
        message = generate_message(check_changes=False)
        if message:
            await context.bot.send_message(
                chat_id=CHANNEL_ID,
                text=message,
                parse_mode='HTML',
                message_thread_id=THREAD_ID
            )
            await update.message.reply_text("✅ Сообщение отправлено в тему группы", parse_mode='HTML')
        else:
            await update.message.reply_text("❌ Не удалось получить данные сервера", parse_mode='HTML')
    except Exception as e:
        logger.error(f"Ошибка отправки в тему: {str(e)}")
        await update.message.reply_text("⚠️ Ошибка при отправке в тему", parse_mode='HTML')

async def handle_server_cmd(update: Update, context: CallbackContext):
    try:
        info = SourceServerQuery.get_info()
        players = SourceServerQuery.get_players()
      
        if not info or not players:
            await update.message.reply_text("❌ Сервер не отвечает", parse_mode='HTML')
            return

        message = [
            f"🔹 <b>{info['name']}</b>",
            f"🗺 Карта: <code>{info['map']}</code>",
            f"👥 Онлайн: <b>{len(players)}/32</b>",
            "\n📊 Игроки:"
        ]

        if players:
            message += [f"👤 {name}" for name in players]
        else:
            message.append("Сейчас никто не играет")

        await update.message.reply_text(
            text="\n".join(message),
            parse_mode='HTML'
        )

    except Exception as e:
        logger.error(f"Ошибка команды: {str(e)}")
        await update.message.reply_text("⚠️ Ошибка при получении данных", parse_mode='HTML')

async def start(update: Update, context: CallbackContext):
    await update.message.reply_text(
        "🤖 Бот мониторинга игрового сервера\n"
        "Используйте команду !сервер для проверки текущего статуса\n"
        "Используйте команду /send_to_thread для отправки в тему группы",
        parse_mode='HTML'
    )

def main():
    try:
        # Создание приложения
        application = Application.builder().token(TOKEN).build()

        # Добавление обработчиков
        application.add_handler(CommandHandler("start", start))
        application.add_handler(CommandHandler("send_to_thread", send_to_thread))
        application.add_handler(MessageHandler(
            filters.TEXT & ~filters.COMMAND & filters.Regex(r'^!сервер'),
            handle_server_cmd
        ))

        # Добавление повторяющегося задания
        application.job_queue.run_repeating(
            send_update,
            interval=UPDATE_INTERVAL,
            first=0
        )

        logger.info("Бот успешно запущен")
        
        # Запуск бота
        application.run_polling()

    except Exception as e:
        logger.critical(f"Критическая ошибка: {str(e)}")
        raise

if __name__ == '__main__':
    main()
 
Последнее редактирование:
Сверху Снизу