Initial commit

This commit is contained in:
Udo Chudo 2024-07-09 13:47:06 +05:00
commit f5ae48a7e9
3 changed files with 538 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/telezab.db
/.env
/.idea

23
requests.txt Normal file
View File

@ -0,0 +1,23 @@
anyio==4.4.0
blinker==1.8.2
certifi==2024.6.2
charset-normalizer==3.3.2
click==8.1.7
colorama==0.4.6
Flask==3.0.3
h11==0.14.0
httpcore==1.0.5
httpx==0.27.0
idna==3.7
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
packaging==24.1
pyTelegramBotAPI==4.19.2
python-dotenv==1.0.1
python-telegram-bot==21.3
requests==2.32.3
sniffio==1.3.1
telebot==0.0.5
urllib3==2.2.2
Werkzeug==3.0.3

512
telezab.py Normal file
View File

@ -0,0 +1,512 @@
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
import re
# 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')
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
''', (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])
# Handle /start command
@bot.message_handler(commands=['start'])
def handle_start(message):
chat_id = message.chat.id
if not is_whitelisted(chat_id):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
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"
bot.send_message(chat_id, "Выполните комманду /register для начала работы")
app.logger.info(f"User {chat_id} ({username}) started receiving alerts.")
# Handle /stop command to stop receiving messages
@bot.message_handler(commands=['stop'])
def handle_stop(message):
chat_id = message.chat.id
if not is_whitelisted(chat_id):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
app.logger.info(f"Unauthorized access attempt by {chat_id}")
return
with db_lock:
conn = sqlite3.connect('telezab.db')
cursor = conn.cursor()
query = 'DELETE FROM subscriptions WHERE chat_id = ?'
app.logger.debug(f"Executing query: {query} with chat_id={chat_id}")
cursor.execute(query, (chat_id,))
conn.commit()
conn.close()
bot.send_message(chat_id, "Вы перестали получать события Zabbix'а :c")
app.logger.info(f"User {chat_id} stopped receiving alerts.")
# Handle /subscribe command to subscribe to a region
@bot.message_handler(commands=['subscribe'])
def handle_subscribe(message):
chat_id = message.chat.id
if not is_whitelisted(chat_id):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
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, "Действие отменено.")
return
region_ids = message.text.split(',')
# Проверка формата ввода
if not all(re.match(r'^\d+$', region_id.strip()) for region_id in region_ids):
bot.send_message(chat_id, "Неверный формат команды. Пожалуйста, введите только цифры, разделенные запятыми.")
bot.register_next_step_handler(message, process_subscription, chat_id, username)
return
invalid_regions = []
already_subscribed = []
with db_lock:
conn = sqlite3.connect('telezab.db')
cursor = conn.cursor()
for region_id in region_ids:
region_id = region_id.strip()
# Проверка существования региона в таблице regions
cursor.execute('SELECT COUNT(*) FROM regions WHERE region_id = ?', (region_id,))
if cursor.fetchone()[0] == 0:
invalid_regions.append(region_id)
continue
# Проверка существующей подписки
cursor.execute('SELECT COUNT(*) FROM subscriptions WHERE chat_id = ? AND region_id = ?', (chat_id, region_id))
if cursor.fetchone()[0] > 0:
already_subscribed.append(region_id)
continue
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()
if invalid_regions:
bot.send_message(chat_id, f"Следующие регионы не существуют: {', '.join(invalid_regions)}. Пожалуйста, проверьте и введите снова.")
bot.register_next_step_handler(message, process_subscription, chat_id, username)
return
if already_subscribed:
bot.send_message(chat_id, f"Вы уже подписаны на следующие регионы: {', '.join(already_subscribed)}.")
if len(already_subscribed) == len(region_ids):
bot.register_next_step_handler(message, process_subscription, chat_id, username)
return
subscribed_regions = [region_id for region_id in region_ids if region_id not in invalid_regions and region_id not in already_subscribed]
if subscribed_regions:
bot.send_message(chat_id, f"Подписка на регионы: {', '.join(subscribed_regions)} оформлена.")
app.logger.info(f"User {chat_id} ({username}) subscribed to regions: {', '.join(subscribed_regions)}.")
else:
bot.send_message(chat_id, "Не удалось оформить подписку на указанные регионы. Пожалуйста, попробуйте снова.")
bot.register_next_step_handler(message, process_subscription, chat_id, username)
# Handle /unsubscribe command to unsubscribe from a region
@bot.message_handler(commands=['unsubscribe'])
def handle_unsubscribe(message):
chat_id = message.chat.id
if not is_whitelisted(chat_id):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
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, "Вы ещё не подписались ни на один регион для получения событий")
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, "Действие отменено.")
return
region_ids = message.text.split(',')
# Проверка формата ввода
if not all(re.match(r'^\d+$', region_id.strip()) for region_id in region_ids):
bot.send_message(chat_id, "Неверный формат команды. Пожалуйста, введите только цифры, разделенные запятыми.")
bot.register_next_step_handler(message, process_unsubscription, chat_id)
return
invalid_unsubscriptions = []
valid_unsubscriptions = []
with db_lock:
conn = sqlite3.connect('telezab.db')
cursor = conn.cursor()
for region_id in region_ids:
region_id = region_id.strip()
# Проверка существования подписки
cursor.execute('SELECT COUNT(*) FROM subscriptions WHERE chat_id = ? AND region_id = ?', (chat_id, region_id))
if cursor.fetchone()[0] == 0:
invalid_unsubscriptions.append(region_id)
else:
valid_unsubscriptions.append(region_id)
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()
if invalid_unsubscriptions:
bot.send_message(chat_id, f"Вы не подписаны на следующие регионы: {', '.join(invalid_unsubscriptions)}.")
if valid_unsubscriptions:
bot.send_message(chat_id, f"Отписка от регионов: {', '.join(valid_unsubscriptions)} выполнена.")
app.logger.info(f"User {chat_id} unsubscribed from regions: {', '.join(valid_unsubscriptions)}.")
if not invalid_unsubscriptions and not valid_unsubscriptions:
bot.send_message(chat_id, "Не удалось выполнить отписку. Пожалуйста, попробуйте снова.")
bot.register_next_step_handler(message, process_unsubscription, chat_id)
# Handle /help command to provide instructions
@bot.message_handler(commands=['help'])
def handle_help(message):
help_text = (
"/subscribe - Подписаться на рассылку событий по региону.\n"
"/unsubscribe - Отписаться от рассылки событий по региону.\n"
"/register - Запросить регистрацию в боте"
)
bot.send_message(message.chat.id, help_text)
# Handle /register command for new user registration
@bot.message_handler(commands=['register'])
def handle_register(message):
chat_id = message.chat.id
username = message.from_user.username
if username:
username = f"@{username}"
else:
username = "N/A"
bot.send_message(chat_id, f"Your chat ID is {chat_id} and your username is {username}. Requesting admin approval...")
for admin_chat_id in ADMIN_CHAT_IDS:
bot.send_message(
admin_chat_id,
f"User {username} ({chat_id}) is requesting to register.\n"
f"Do you approve this action?\n"
f"/add_whitelist {chat_id}"
)
app.logger.info(f"User {chat_id} ({username}) requested registration.")
# Handle /add_whitelist command to add a user to the whitelist (Admin only)
@bot.message_handler(commands=['add_whitelist'])
def handle_add_whitelist(message):
chat_id = message.chat.id
if str(chat_id) not in ADMIN_CHAT_IDS:
bot.send_message(chat_id, "Вы не авторизованы для использования этой команды.")
app.logger.info(f"Unauthorized admin command attempt by {chat_id}")
return
try:
new_chat_id = int(message.text.split()[1])
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 command format. Use /add_whitelist <chat_id>")
# Handle /remove_whitelist command to remove a user from the whitelist (Admin only)
@bot.message_handler(commands=['remove_whitelist'])
def handle_remove_whitelist(message):
chat_id = message.chat.id
if str(chat_id) not in ADMIN_CHAT_IDS:
bot.send_message(chat_id, "Вы не авторизованы для использования этой команды.")
app.logger.info(f"Unauthorized admin command attempt by {chat_id}")
return
try:
remove_chat_id = int(message.text.split()[1])
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 command format. Use /remove_whitelist <chat_id>")
# Handle /add_region command to add a new region (Admin only)
@bot.message_handler(commands=['add_region'])
def handle_add_region(message):
chat_id = message.chat.id
if str(chat_id) not in ADMIN_CHAT_IDS:
bot.send_message(chat_id, "Вы не авторизованы для использования этой команды.")
app.logger.info(f"Unauthorized admin command attempt by {chat_id}")
return
try:
region_id, region_name = message.text.split()[1], ' '.join(message.text.split()[2:])
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 command format. Use /add_region <region_id> <region_name>")
# Handle /remove_region command to remove a region (Admin only)
@bot.message_handler(commands=['remove_region'])
def handle_remove_region(message):
chat_id = message.chat.id
if str(chat_id) not in ADMIN_CHAT_IDS:
bot.send_message(chat_id, "Вы не авторизованы для использования этой команды.")
app.logger.info(f"Unauthorized admin command attempt by {chat_id}")
return
try:
region_id = message.text.split()[1]
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 command format. Use /remove_region <region_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()