Telezab/telezab.py

478 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 и название региона в формате: <region_id> <region_name>")
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: <region_id> <region_name>")
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: <region_id>")
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: <chat_id>")
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: <chat_id>")
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()