from flask import Flask, request, jsonify import telebot from dotenv import load_dotenv import os import hashlib import logging from logging.config import dictConfig from threading import Thread, Lock import sqlite3 import time # Load environment variables load_dotenv() # Configure logging DEBUG_LOGGING = os.getenv('DEBUG_LOGGING', 'false').lower() == 'true' if DEBUG_LOGGING: log_level = 'DEBUG' else: log_level = 'INFO' dictConfig({ 'version': 1, 'formatters': {'default': { 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s', }}, 'handlers': {'wsgi': { 'class': 'logging.StreamHandler', 'stream': 'ext://flask.logging.wsgi_errors_stream', 'formatter': 'default' }}, 'root': { 'level': log_level, 'handlers': ['wsgi'] } }) app = Flask(__name__) # Get the token from environment variables TOKEN = os.getenv('TELEGRAM_TOKEN') if not TOKEN: raise ValueError("No TELEGRAM_TOKEN found in environment variables") ADMIN_CHAT_IDS = os.getenv('ADMIN_CHAT_IDS', '').split(',') bot = telebot.TeleBot(TOKEN) # Lock for database operations db_lock = Lock() # Initialize SQLite database def init_db(): with db_lock: conn = sqlite3.connect('telezab.db') cursor = conn.cursor() # Create events table cursor.execute('''CREATE TABLE IF NOT EXISTS events ( id INTEGER PRIMARY KEY AUTOINCREMENT, hash TEXT UNIQUE, data TEXT, delivered BOOLEAN)''') # Create subscriptions table with username cursor.execute('''CREATE TABLE IF NOT EXISTS subscriptions ( chat_id INTEGER, region_id TEXT, username TEXT, skip BOOLEAN DEFAULT FALSE, UNIQUE(chat_id, region_id))''') # Create whitelist table cursor.execute('''CREATE TABLE IF NOT EXISTS whitelist ( chat_id INTEGER PRIMARY KEY)''') # Create regions table cursor.execute('''CREATE TABLE IF NOT EXISTS regions ( region_id TEXT PRIMARY KEY, region_name TEXT)''') # Insert sample regions cursor.execute('''INSERT OR IGNORE INTO regions (region_id, region_name) VALUES ('01', 'Адыгея'), ('02', 'Башкортостан (Уфа)'), ('04', 'Алтай'), ('19', 'Республика Хакасия')''') conn.commit() conn.close() # Hash the incoming data def hash_data(data): return hashlib.sha256(str(data).encode('utf-8')).hexdigest() # Check if user is in whitelist def is_whitelisted(chat_id): with db_lock: conn = sqlite3.connect('telezab.db') cursor = conn.cursor() query = 'SELECT COUNT(*) FROM whitelist WHERE chat_id = ?' app.logger.debug(f"Executing query: {query} with chat_id={chat_id}") cursor.execute(query, (chat_id,)) count = cursor.fetchone()[0] conn.close() return count > 0 # Add user to whitelist def add_to_whitelist(chat_id): with db_lock: conn = sqlite3.connect('telezab.db') cursor = conn.cursor() query = 'INSERT OR IGNORE INTO whitelist (chat_id) VALUES (?)' app.logger.debug(f"Executing query: {query} with chat_id={chat_id}") cursor.execute(query, (chat_id,)) conn.commit() conn.close() # Remove user from whitelist def remove_from_whitelist(chat_id): with db_lock: conn = sqlite3.connect('telezab.db') cursor = conn.cursor() query = 'DELETE FROM whitelist WHERE chat_id = ?' app.logger.debug(f"Executing query: {query} with chat_id={chat_id}") cursor.execute(query, (chat_id,)) conn.commit() conn.close() # Get list of regions def get_regions(): with db_lock: conn = sqlite3.connect('telezab.db') cursor = conn.cursor() cursor.execute('SELECT region_id, region_name FROM regions ORDER BY region_id') regions = cursor.fetchall() conn.close() return regions # Get list of regions a user is subscribed to def get_user_subscribed_regions(chat_id): with db_lock: conn = sqlite3.connect('telezab.db') cursor = conn.cursor() cursor.execute(''' SELECT regions.region_id, regions.region_name FROM subscriptions JOIN regions ON subscriptions.region_id = regions.region_id WHERE subscriptions.chat_id = ? AND subscriptions.skip = FALSE ORDER BY regions.region_id ''', (chat_id,)) regions = cursor.fetchall() conn.close() return regions # Format regions list def format_regions_list(regions): return '\n'.join([f"{region_id} - {region_name}" for region_id, region_name in regions]) # Main menu for users def show_main_menu(chat_id, is_whitelisted_user): markup = telebot.types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True) if is_whitelisted_user: markup.add('Подписаться', 'Отписаться', 'Помощь', 'Add Region', 'Remove Region', 'Add Whitelist', 'Remove Whitelist') else: markup.add('Register') bot.send_message(chat_id, "Выберите действие:", reply_markup=markup) # Handle /start command @bot.message_handler(commands=['start']) def handle_start(message): chat_id = message.chat.id username = message.from_user.username if username: username = f"@{username}" else: username = "N/A" if is_whitelisted(chat_id): show_main_menu(chat_id, is_whitelisted_user=True) else: show_main_menu(chat_id, is_whitelisted_user=False) app.logger.info(f"User {chat_id} ({username}) started with command /start.") # Handle menu button presses @bot.message_handler(func=lambda message: True) def handle_menu_selection(message): chat_id = message.chat.id text = message.text.strip().lower() if text == 'register': handle_register(message) elif text == 'подписаться': handle_subscribe(message) elif text == 'отписаться': handle_unsubscribe(message) elif text == 'помощь': handle_help(message) elif text == 'add region' and str(chat_id) in ADMIN_CHAT_IDS: prompt_admin_for_region(chat_id, 'add') elif text == 'remove region' and str(chat_id) in ADMIN_CHAT_IDS: prompt_admin_for_region(chat_id, 'remove') elif text == 'add whitelist' and str(chat_id) in ADMIN_CHAT_IDS: prompt_admin_for_whitelist(chat_id, 'add') elif text == 'remove whitelist' and str(chat_id) in ADMIN_CHAT_IDS: prompt_admin_for_whitelist(chat_id, 'remove') else: bot.send_message(chat_id, "Команда не распознана или у вас нет прав для выполнения этой команды.") show_main_menu(chat_id, is_whitelisted(chat_id)) # Handle /subscribe command to subscribe to a region def handle_subscribe(message): chat_id = message.chat.id if not is_whitelisted(chat_id): bot.send_message(chat_id, "You are not authorized to use this bot.") app.logger.info(f"Unauthorized access attempt by {chat_id}") return username = message.from_user.username if username: username = f"@{username}" else: username = "N/A" regions_list = format_regions_list(get_regions()) bot.send_message(chat_id, f"Отправьте номер или номера регионов на которые хотите подписаться (через запятую):\n{regions_list}\n\nНапишите 'отмена' для отмены.") bot.register_next_step_handler(message, process_subscription, chat_id, username) def process_subscription(message, chat_id, username): if message.text.lower() == 'отмена': bot.send_message(chat_id, "Действие отменено.") show_main_menu(chat_id, is_whitelisted(chat_id)) return region_ids = message.text.split(',') with db_lock: conn = sqlite3.connect('telezab.db') cursor = conn.cursor() for region_id in region_ids: region_id = region_id.strip() query = 'INSERT OR IGNORE INTO subscriptions (chat_id, region_id, username) VALUES (?, ?, ?)' app.logger.debug(f"Executing query: {query} with chat_id={chat_id}, region_id={region_id}, username={username}") cursor.execute(query, (chat_id, region_id, username)) conn.commit() conn.close() bot.send_message(chat_id, f"Subscribed to regions: {', '.join(region_ids)}.") app.logger.info(f"User {chat_id} ({username}) subscribed to regions: {', '.join(region_ids)}.") show_main_menu(chat_id, is_whitelisted(chat_id)) # Handle /unsubscribe command to unsubscribe from a region def handle_unsubscribe(message): chat_id = message.chat.id if not is_whitelisted(chat_id): bot.send_message(chat_id, "You are not authorized to use this bot.") app.logger.info(f"Unauthorized access attempt by {chat_id}") return user_regions = get_user_subscribed_regions(chat_id) if not user_regions: bot.send_message(chat_id, "You are not subscribed to any regions.") else: regions_list = format_regions_list(user_regions) bot.send_message(chat_id, f"Отправьте номер или номера регионов от которых хотите отписаться (через запятую):\n{regions_list}\n\nНапишите 'отмена' для отмены.") bot.register_next_step_handler(message, process_unsubscription, chat_id) def process_unsubscription(message, chat_id): if message.text.lower() == 'отмена': bot.send_message(chat_id, "Действие отменено.") show_main_menu(chat_id, is_whitelisted(chat_id)) return region_ids = message.text.split(',') with db_lock: conn = sqlite3.connect('telezab.db') cursor = conn.cursor() for region_id in region_ids: region_id = region_id.strip() query = 'DELETE FROM subscriptions WHERE chat_id = ? AND region_id = ?' app.logger.debug(f"Executing query: {query} with chat_id={chat_id}, region_id={region_id}") cursor.execute(query, (chat_id, region_id)) conn.commit() conn.close() bot.send_message(chat_id, f"Unsubscribed from regions: {', '.join(region_ids)}.") app.logger.info(f"User {chat_id} unsubscribed from regions: {', '.join(region_ids)}.") show_main_menu(chat_id, is_whitelisted(chat_id)) # Handle /help command to provide instructions def handle_help(message): help_text = ( "/start - Начать работу с ботом\n" "/subscribe - Подписаться на рассылку событий по региону. Необходимо указать номер региона пример /subscribe 01 - Адыгея\n" "/unsubscribe - Отписаться от рассылки событий по региону. Необходимо указать номер региона пример /unsubscribe 01 - Адыгея\n" "/register - Запросить регистрацию в боте" ) bot.send_message(message.chat.id, help_text) show_main_menu(message.chat.id, is_whitelisted(message.chat.id)) # Handle /register command for new user registration def handle_register(message): chat_id = message.chat.id username = message.from_user.username if username: username = f"@{username}" else: username = "N/A" markup = telebot.types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True) markup.add('Подтвердить регистрацию', 'Отмена') bot.send_message(chat_id, f"Your chat ID is {chat_id} and your username is {username}. Requesting admin approval...", reply_markup=markup) bot.register_next_step_handler(message, process_register, chat_id, username) def process_register(message, chat_id, username): if message.text.lower() == 'отмена': bot.send_message(chat_id, "Регистрация отменена.") show_main_menu(chat_id, is_whitelisted(chat_id)) return if message.text.lower() == 'подтвердить регистрацию': for admin_chat_id in ADMIN_CHAT_IDS: markup = telebot.types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True) markup.add(f'/add_whitelist {chat_id}', 'Отмена') bot.send_message( admin_chat_id, f"User {username} ({chat_id}) is requesting to register.\n" f"Do you approve this action?", reply_markup=markup ) bot.send_message(chat_id, "Запрос отправлен администратору для одобрения.") app.logger.info(f"User {chat_id} ({username}) requested registration.") else: bot.send_message(chat_id, "Некорректный выбор. Регистрация отменена.") show_main_menu(chat_id, is_whitelisted(chat_id)) # Handle admin region management commands def prompt_admin_for_region(chat_id, action): if action == 'add': bot.send_message(chat_id, "Введите ID и название региона в формате: ") bot.register_next_step_handler_by_chat_id(chat_id, process_add_region) elif action == 'remove': bot.send_message(chat_id, "Введите ID региона, который хотите удалить") bot.register_next_step_handler_by_chat_id(chat_id, process_remove_region) def process_add_region(message): chat_id = message.chat.id try: region_id, region_name = message.text.split()[0], ' '.join(message.text.split()[1:]) with db_lock: conn = sqlite3.connect('telezab.db') cursor = conn.cursor() query = 'INSERT OR IGNORE INTO regions (region_id, region_name) VALUES (?, ?)' app.logger.debug(f"Executing query: {query} with region_id={region_id}, region_name={region_name}") cursor.execute(query, (region_id, region_name)) conn.commit() conn.close() bot.send_message(chat_id, f"Region {region_id} - {region_name} added.") app.logger.info(f"Admin {chat_id} added region {region_id} - {region_name}.") except (IndexError, ValueError): bot.send_message(chat_id, "Invalid format. Use: ") show_main_menu(chat_id, is_whitelisted(chat_id)) def process_remove_region(message): chat_id = message.chat.id try: region_id = message.text.split()[0] with db_lock: conn = sqlite3.connect('telezab.db') cursor = conn.cursor() query = 'DELETE FROM regions WHERE region_id = ?' app.logger.debug(f"Executing query: {query} with region_id={region_id}") cursor.execute(query, (region_id,)) query = 'UPDATE subscriptions SET skip = TRUE WHERE region_id = ?' app.logger.debug(f"Executing query: {query} with region_id={region_id}") cursor.execute(query, (region_id,)) conn.commit() conn.close() bot.send_message(chat_id, f"Region {region_id} removed and all subscriptions updated.") app.logger.info(f"Admin {chat_id} removed region {region_id} and updated subscriptions.") except IndexError: bot.send_message(chat_id, "Invalid format. Use: ") show_main_menu(chat_id, is_whitelisted(chat_id)) # Handle admin whitelist management commands def prompt_admin_for_whitelist(chat_id, action): if action == 'add': bot.send_message(chat_id, "Введите ID пользователя, которого хотите добавить в whitelist") bot.register_next_step_handler_by_chat_id(chat_id, process_add_whitelist) elif action == 'remove': bot.send_message(chat_id, "Введите ID пользователя, которого хотите удалить из whitelist") bot.register_next_step_handler_by_chat_id(chat_id, process_remove_whitelist) def process_add_whitelist(message): chat_id = message.chat.id try: new_chat_id = int(message.text.split()[0]) add_to_whitelist(new_chat_id) bot.send_message(chat_id, f"Chat ID {new_chat_id} added to the whitelist.") app.logger.info(f"Admin {chat_id} added {new_chat_id} to the whitelist.") except (IndexError, ValueError): bot.send_message(chat_id, "Invalid format. Use: ") show_main_menu(chat_id, is_whitelisted(chat_id)) def process_remove_whitelist(message): chat_id = message.chat.id try: remove_chat_id = int(message.text.split()[0]) remove_from_whitelist(remove_chat_id) bot.send_message(chat_id, f"Chat ID {remove_chat_id} removed from the whitelist.") app.logger.info(f"Admin {chat_id} removed {remove_chat_id} from the whitelist.") except (IndexError, ValueError): bot.send_message(chat_id, "Invalid format. Use: ") show_main_menu(chat_id, is_whitelisted(chat_id)) @app.route('/webhook', methods=['POST']) def webhook(): data = request.get_json() app.logger.info(f"Received data: {data}") event_hash = hash_data(data) with db_lock: conn = sqlite3.connect('telezab.db') cursor = conn.cursor() cursor.execute('SELECT COUNT(*) FROM events') count = cursor.fetchone()[0] if count >= 200: query = 'DELETE FROM events WHERE id = (SELECT MIN(id) FROM events)' app.logger.debug(f"Executing query: {query}") cursor.execute(query) query = 'INSERT OR IGNORE INTO events (hash, data, delivered) VALUES (?, ?, ?)' app.logger.debug(f"Executing query: {query} with hash={event_hash}, data={data}, delivered={False}") cursor.execute(query, (event_hash, str(data), False)) # Fetch chat_ids to send the alert region_id = data.get("region") query = 'SELECT chat_id, username FROM subscriptions WHERE region_id = ? AND skip = FALSE' app.logger.debug(f"Executing query: {query} with region_id={region_id}") cursor.execute(query, (region_id,)) results = cursor.fetchall() message = format_message(data) for chat_id, username in results: try: app.logger.debug(f"Sending message: {message} to chat_id={chat_id}, username={username}") bot.send_message(chat_id, message) app.logger.info(f"Sent alert to {chat_id} ({username}) for region {region_id}") except telebot.apihelper.ApiTelegramException as e: app.logger.error(f"Failed to send message to {chat_id} ({username}): {e}") except Exception as e: app.logger.error(f"Error sending message to {chat_id} ({username}): {e}") conn.commit() conn.close() return jsonify({"status": "success"}), 200 def format_message(data): return (f"Zabbix Alert\n" f"Host: {data['host']}\n" f"Item: {data['item']}\n" f"Trigger: {data['trigger']}\n" f"Value: {data['value']}") def run_polling(): bot.polling(none_stop=True, interval=0) if __name__ == '__main__': init_db() # Start Flask app in a separate thread Thread(target=app.run, kwargs={'port': 5000, 'debug': False}, daemon=True).start() # Start bot polling run_polling()