Compare commits

..

No commits in common. "e55e330c5054cc1da441c8833cc65732f4462fc2" and "b7433f0c995263e2265e0937894e0a2fcbce2edb" have entirely different histories.

12 changed files with 98 additions and 266 deletions

View File

@ -1,13 +1,13 @@
from . import cancel_input
from . import debug
from . import help
from . import subscribe, active_triggers
from . import unsubscribe
from . import my_subscriptions
from . import cancel_input
from . import notification_switch_mode
from . import help
from . import registration
from . import settings
from . import start
from . import subscribe, active_triggers
from . import unsubscribe
from . import debug
from ..states import UserStateManager
state_manager = UserStateManager()
@ -31,6 +31,4 @@ def register_handlers(bot, app):
def register_callbacks(bot, app):
notification_switch_mode.register_callback_notification(bot, app, state_manager)
active_triggers.register_callbacks_active_triggers(bot, app, state_manager)
cancel_input.register_callback_cancel_input(bot,state_manager)
subscribe.register_callback_subscribe(bot, app, state_manager)
unsubscribe.register_callback_unsubscribe(bot, app, state_manager)
cancel_input.register_callback_cancel_input(bot,state_manager)

View File

@ -1,9 +1,8 @@
import telebot
from telebot.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
from app.bot.keyboards.main_menu import get_main_menu
from app import Subscriptions, db
from app.bot.constants import UserStates
from app.bot.keyboards.main_menu import get_main_menu
from app.bot.keyboards.settings_menu import get_settings_menu
from app.bot.utils.auth import auth
from app.bot.utils.tg_audit import log_user_event
@ -61,7 +60,7 @@ def register_callback_notification(bot, app, state_manager):
mode_text_emoji = "⛔️ Критические события" if disaster_only else "⚠️ Все события"
mode_text = "Критические события" if disaster_only else "Все события"
bot.send_message(chat_id, f"🔄 Режим уведомлений успешно изменён на:\n {mode_text_emoji}",reply_markup=get_settings_menu())
bot.send_message(chat_id, f" Режим уведомлений успешно изменён на:\n {mode_text_emoji}",reply_markup=get_settings_menu())
log_user_event(chat_id, app, username, f"Режим уведомлений изменился на: {mode_text}")
state_manager.set_state(chat_id, UserStates.SETTINGS_MENU)

View File

@ -1,13 +1,13 @@
from telebot import TeleBot, logger
from telebot.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from telebot.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
from app import Subscriptions
from app.bot.constants import UserStates
from app.bot.processors.subscribe_processor import process_subscription_button, process_subscribe_all_regions
from app.bot.processors.subscribe_processor import process_subscription_button
from app.bot.states import UserStateManager
from app.bot.utils.auth import auth
from app.bot.utils.regions import get_sorted_regions, format_regions_list_marked
from app.bot.utils.regions import get_sorted_regions, format_regions_list, format_regions_list_marked
from telebot import TeleBot, logger
def register_handlers(bot: TeleBot, app, state_manager: UserStateManager):
@ -36,7 +36,6 @@ def register_handlers(bot: TeleBot, app, state_manager: UserStateManager):
regions_text = format_regions_list_marked(regions, subscribed)
markup = InlineKeyboardMarkup()
markup.add(InlineKeyboardButton("Подписаться на все регионы", callback_data="subscribe_all"))
markup.add(InlineKeyboardButton(text="Отмена", callback_data="cancel_input"))
bot_message = bot.send_message(chat_id,
@ -45,17 +44,3 @@ def register_handlers(bot: TeleBot, app, state_manager: UserStateManager):
bot.register_next_step_handler(message, process_subscription_button, app, bot, chat_id, state_manager, bot_message.message_id)
def register_callback_subscribe(bot: TeleBot, app, state_manager):
@bot.callback_query_handler(func=lambda call: call.data == "subscribe_all")
def handle_subscribe_all_button(call: CallbackQuery):
chat_id = call.message.chat.id
username = f"{call.from_user.username}" if call.from_user.username else "N/A"
with app.app_context():
if not auth(chat_id, app):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
logger.warning(f"Неавторизованный пользователь {chat_id} @{username}")
state_manager.set_state(chat_id, UserStates.REGISTRATION)
return
process_subscribe_all_regions(call, app, bot, chat_id, state_manager)

View File

@ -0,0 +1,15 @@
# app/bot/handlers/settings.py
from telebot.types import Message
from app.bot.keyboards.main_menu import get_main_menu
from app.bot.keyboards.settings_menu import get_settings_menu
def register_handlers(bot,app):
@bot.message_handler(func=lambda msg: msg.text == "Режим уведомлений")
def handle_notify_mode(message: Message):
bot.send_message(message.chat.id, "⚙️ Настройка режима уведомлений пока не реализована.")
@bot.message_handler(func=lambda msg: msg.text == "Назад")
def handle_back(message: Message):
bot.send_message(message.chat.id, "Возврат в главное меню", reply_markup=get_main_menu())

View File

@ -1,12 +1,10 @@
from telebot import logger, TeleBot
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
from telebot import logger
from app.bot.constants import UserStates
from app.bot.keyboards.settings_menu import get_settings_menu
from app.bot.processors.unsubscribe_processor import process_unsubscribe_button, process_unsubscribe_all_regions
from app.bot.utils.auth import auth
from app.bot.utils.helpers import get_user_subscribed_regions
from app.bot.utils.regions import format_regions_list
from app.bot.processors.unsubscribe_processor import process_unsubscribe_button
def register_handlers(bot, app, state_manager):
@ -16,48 +14,22 @@ def register_handlers(bot, app, state_manager):
with app.app_context():
chat_id = message.chat.id
username = f"{message.from_user.username}" if message.from_user.username else "N/A"
if not auth(chat_id, app):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
logger.warning(f"Неавторизованный пользователь {chat_id} @{username}")
state_manager.set_state(chat_id, UserStates.REGISTRATION)
return
else:
state_manager.set_state(chat_id, UserStates.WAITING_INPUT)
user_subscriptions = get_user_subscribed_regions(chat_id)
# ✅ Предварительная проверка: есть ли подписки
if not user_subscriptions:
bot.send_message(chat_id, " У вас нет активных подписок для отписки.",reply_markup=get_settings_menu())
state_manager.set_state(chat_id, UserStates.SETTINGS_MENU)
return
# Есть подписки — предлагаем меню отписки
state_manager.set_state(chat_id, UserStates.WAITING_INPUT)
formated_user_subscriptions = format_regions_list(user_subscriptions)
markup = InlineKeyboardMarkup()
markup.add(InlineKeyboardButton("Отписаться от всех регионов", callback_data="unsubscribe_all"))
markup.add(InlineKeyboardButton("Отмена", callback_data="cancel_input"))
bot.send_message(chat_id,
f"Введите номер(а) региона(ов) через запятую, от которых вы хотите отписаться:\n\n{formated_user_subscriptions}",
f"Введите номер(а) региона(ов) через запятую подписки которых вы хотите удалить:\n\n{formated_user_subscriptions}",
reply_markup=markup)
bot.register_next_step_handler(message, process_unsubscribe_button, app, bot, chat_id, state_manager)
def register_callback_unsubscribe(bot: TeleBot, app, state_manager):
@bot.callback_query_handler(func=lambda call: call.data == "unsubscribe_all")
def handle_subscribe_all_button(call: CallbackQuery):
chat_id = call.message.chat.id
username = f"{call.from_user.username}" if call.from_user.username else "N/A"
with app.app_context():
if not auth(chat_id, app):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
logger.warning(f"Неавторизованный пользователь {chat_id} @{username}")
state_manager.set_state(chat_id, UserStates.REGISTRATION)
return
process_unsubscribe_all_regions(call, app, bot, state_manager)
bot.register_next_step_handler(message, process_unsubscribe_button, app, bot, chat_id, state_manager)

View File

@ -75,50 +75,3 @@ def process_subscription_button(message: Message, app, bot, chat_id: int, state_
# Показываем меню
bot.send_message(chat_id, f"✅ Подписка на регионы: {', '.join(subbed_regions)} оформлена.", reply_markup=get_settings_menu())
def process_subscribe_all_regions(call, app, bot, chat_id: int, state_manager):
try:
with app.app_context():
username = f"{call.from_user.username}" if call.from_user.username else "N/A"
# Получаем все активные регионы
active_regions = Regions.query.filter_by(active=True).all()
active_region_ids = {r.region_id for r in active_regions}
# Получаем уже подписанные регионы
existing_subs = Subscriptions.query.filter_by(chat_id=chat_id).all()
existing_region_ids = {sub.region_id for sub in existing_subs if sub.active}
newly_added = []
for region in active_regions:
if region.region_id in existing_region_ids:
continue
existing = next((sub for sub in existing_subs if sub.region_id == region.region_id), None)
if existing:
existing.active = True
db.session.add(existing)
else:
new_sub = Subscriptions(chat_id=chat_id, region_id=region.region_id, active=True)
db.session.add(new_sub)
newly_added.append(str(region.region_id))
db.session.commit()
# Логирование действия
if newly_added:
log_user_event(chat_id, app, username, f"Подписался на все регионы: {', '.join(newly_added)}")
bot.answer_callback_query(call.id)
bot.clear_step_handler_by_chat_id(chat_id)
bot.delete_message(chat_id, call.message.message_id)
bot.send_message(chat_id,
"✅ Вы подписались на все доступные регионы.",
reply_markup=get_settings_menu())
state_manager.set_state(chat_id, UserStates.SETTINGS_MENU)
except Exception as e:
bot.send_message(chat_id, "⚠️ Не удалось выполнить подписку. Попробуйте позже.")

View File

@ -1,15 +1,13 @@
from flask import Flask
from telebot import TeleBot, logger
from telebot.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from app import Subscriptions
from app.bot.constants import UserStates
from telebot.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
from app.bot.keyboards.settings_menu import get_settings_menu
from app.bot.states import UserStateManager
from app.bot.utils.helpers import get_user_subscribed_regions
from app import Subscriptions
from app.bot.utils.tg_audit import log_user_event
from app.extensions.db import db
from app.bot.states import UserStateManager
from app.bot.constants import UserStates
def process_unsubscribe_button(message: Message, app: Flask, bot: TeleBot, chat_id: int, state_manager: UserStateManager):
unsubbed_regions = []
@ -64,39 +62,4 @@ def process_unsubscribe_button(message: Message, app: Flask, bot: TeleBot, chat_
f"⚠ Регионы с ID {', '.join(invalid_regions)} не найдены среди ваших подписок и не были изменены.")
state_manager.set_state(chat_id, UserStates.SETTINGS_MENU)
bot.send_message(chat_id, "⚙ Вернулись в меню настроек.", reply_markup=get_settings_menu())
def process_unsubscribe_all_regions(call: CallbackQuery, app: Flask, bot: TeleBot, state_manager: UserStateManager):
chat_id = call.message.chat.id
username = f"{call.from_user.username}" if call.from_user.username else "N/A"
try:
with app.app_context():
subscriptions = Subscriptions.query.filter_by(chat_id=chat_id, active=True).all()
if not subscriptions:
bot.answer_callback_query(call.id, text="У вас нет активных подписок.")
return
for sub in subscriptions:
sub.active = False
db.session.add(sub)
db.session.commit()
log_user_event(chat_id, app, username, "Отписался от всех регионов")
bot.answer_callback_query(call.id)
bot.clear_step_handler_by_chat_id(chat_id)
bot.delete_message(chat_id, call.message.message_id)
bot.send_message(chat_id,
"✅ Вы успешно отписались от всех регионов.",
reply_markup=get_settings_menu())
state_manager.set_state(chat_id, UserStates.SETTINGS_MENU)
except Exception as e:
state_manager.set_state(chat_id, UserStates.SETTINGS_MENU)
bot.send_message(chat_id, "⚠ Произошла ошибка при отписке. Попробуйте позже.",reply_markup=get_settings_menu())
return
bot.send_message(chat_id, "⚙ Вернулись в меню настроек.", reply_markup=get_settings_menu())

View File

@ -1,21 +1,17 @@
import asyncio
import json
import logging
from typing import Optional
from telebot import apihelper
from aio_pika import connect_robust, Message, DeliveryMode
from aio_pika.abc import AbstractIncomingMessage
from telebot import apihelper
from app.bot.services.mailing_service.parser import parse_region_id
from app.bot.services.mailing_service.composer import compose_telegram_message
from app.bot.services.mailing_service.db_utils import get_recipients_by_region
from app.bot.services.mailing_service.parser import parse_region_id
from app.bot.services.mailing_service.utils.link_button import create_link_button
from app.bot.services.mailing_service.utils.tg_get_user import get_telegram_id_by_chat_id
from config import RABBITMQ_URL_FULL, RABBITMQ_QUEUE, RABBITMQ_NOTIFICATIONS_QUEUE, MAILING_RATE_LIMIT
from config import RABBITMQ_URL_FULL, RABBITMQ_QUEUE, RABBITMQ_NOTIFICATIONS_QUEUE
logger = logging.getLogger("TeleBot")
rate_limit_semaphore = asyncio.Semaphore(MAILING_RATE_LIMIT)
rate_limit_semaphore = asyncio.Semaphore(25)
class AsyncMailingService:
def __init__(self, flask_app, bot):
@ -32,7 +28,7 @@ class AsyncMailingService:
async def consume_raw_messages(self):
while True:
try:
logger.info("[MailingService][raw] Подключение к RabbitMQ (сырые сообщения)...")
logger.info("[MailingService] Подключение к RabbitMQ (сырые сообщения)...")
connection = await connect_robust(RABBITMQ_URL_FULL, loop=self.loop)
async with connection:
channel = await connection.channel()
@ -40,14 +36,14 @@ class AsyncMailingService:
raw_queue = await channel.declare_queue(RABBITMQ_QUEUE, durable=True)
notifications_queue = await channel.declare_queue(RABBITMQ_NOTIFICATIONS_QUEUE, durable=True)
logger.info("[MailingService][raw] Ожидание сообщений из очереди...")
logger.info("[MailingService] Ожидание сообщений из очереди...")
async with raw_queue.iterator() as queue_iter:
async for message in queue_iter:
await self.handle_raw_message(message, channel, notifications_queue)
except Exception as e:
logger.error(f"[MailingService][raw] Ошибка подключения или обработки: {e}")
logger.info("[MailingService][raw] Повторное подключение через 5 секунд...")
logger.error(f"[MailingService] Ошибка подключения или обработки: {e}")
logger.info("[MailingService] Повторное подключение через 5 секунд...")
await asyncio.sleep(5)
async def handle_raw_message(self, message: AbstractIncomingMessage, channel, notifications_queue):
@ -55,9 +51,9 @@ class AsyncMailingService:
with self.flask_app.app_context():
try:
data = json.loads(message.body.decode("utf-8"))
logger.info(f"[MailingService][raw] Получены данные: {json.dumps(data, ensure_ascii=False)}")
logger.info(f"[MailingService] Получено сообщение: {json.dumps(data, ensure_ascii=False)}")
region_id = int(parse_region_id(data.get("hostgroups")))
region_id = parse_region_id(data.get("host"))
severity = data.get("severity", "")
# Получатели и сообщение параллельно
@ -65,8 +61,12 @@ class AsyncMailingService:
message_task = asyncio.to_thread(compose_telegram_message, data)
recipients, (final_message, link) = await asyncio.gather(recipients_task, message_task)
logger.info(f"[MailingService][raw] Формирование списка получателей ({len(recipients)})")
logger.debug(f"[MailingService][raw] Список получателей: {', '.join(map(str, recipients))}")
logger.info(f"[MailingService] Получатели: {recipients}")
if link:
logger.info(f"[MailingService] Сообщение для Telegram: {final_message} {link}")
else:
logger.info(f"[MailingService] Сообщение для Telegram: {final_message}")
# Формируем и публикуем индивидуальные уведомления в очередь отправки
for chat_id in recipients:
@ -79,29 +79,29 @@ class AsyncMailingService:
msg = Message(body, delivery_mode=DeliveryMode.PERSISTENT)
await channel.default_exchange.publish(msg, routing_key=notifications_queue.name)
logger.info(f"[MailingService][raw] Поставлено в очередь уведомлений: {len(recipients)} сообщений")
logger.info(f"[MailingService] Поставлено в очередь уведомлений: {len(recipients)} сообщений")
except Exception as e:
logger.exception(f"[MailingService][raw] Ошибка обработки сообщения: {e}")
logger.exception(f"[MailingService] Ошибка обработки сообщения: {e}")
async def consume_notifications(self):
while True:
try:
logger.info("[MailingService][formated] Подключение к RabbitMQ (уведомления для отправки)...")
logger.info("[MailingService] Подключение к RabbitMQ (уведомления для отправки)...")
connection = await connect_robust(RABBITMQ_URL_FULL, loop=self.loop)
async with connection:
channel = await connection.channel()
await channel.set_qos(prefetch_count=5)
notif_queue = await channel.declare_queue(RABBITMQ_NOTIFICATIONS_QUEUE, durable=True)
logger.info("[MailingService][formated] Ожидание сообщений из очереди уведомлений...")
logger.info("[MailingService] Ожидание сообщений из очереди уведомлений...")
async with notif_queue.iterator() as queue_iter:
async for message in queue_iter:
await self.handle_notification_message(message)
except Exception as e:
logger.error(f"[MailingService][formated] Ошибка подключения или обработки уведомлений: {e}")
logger.info("[MailingService][formated] Повторное подключение через 5 секунд...")
logger.error(f"[MailingService] Ошибка подключения или обработки уведомлений: {e}")
logger.info("[MailingService] Повторное подключение через 5 секунд...")
await asyncio.sleep(5)
async def handle_notification_message(self, message: AbstractIncomingMessage):
@ -111,60 +111,41 @@ class AsyncMailingService:
chat_id = data.get("chat_id")
message_text = data.get("message")
link = data.get("link")
if link:
message_text = f"{message_text} {link}"
# TODO: расширить логику отправки с кнопкой, если нужно
await self.send_message(chat_id, message_text, link)
await self.send_message(chat_id, message_text)
except Exception as e:
logger.error(f"[MailingService][tg message] Ошибка отправки уведомления: {e}")
logger.error(f"[MailingService] Ошибка отправки уведомления: {e}")
# Можно реализовать message.nack() для повторной попытки
async def send_message(self, chat_id, message, link: Optional[str] = None):
async def send_message(self, chat_id, message):
telegram_id = "unknown"
try:
await rate_limit_semaphore.acquire()
# Получаем telegram_id в отдельном потоке
telegram_id = await asyncio.to_thread(get_telegram_id_by_chat_id, self.flask_app, chat_id)
def get_telegram_id():
with self.flask_app.app_context():
from app.models.users import Users
user = Users.query.filter_by(chat_id=chat_id).first()
return user.telegram_id if user else "unknown"
# Формируем клавиатуру с кнопкой
link_button = create_link_button(link)
telegram_id = await asyncio.to_thread(get_telegram_id)
await asyncio.to_thread(
self.bot.send_message,
chat_id,
message,
parse_mode="HTML",
reply_markup=link_button
)
await asyncio.to_thread(self.bot.send_message, chat_id, message, parse_mode="HTML")
formatted_message = message.replace("\n", " ").replace("\r", "")
logger.info(f"[MailingService][tg message] Отправлено уведомление {telegram_id} ({chat_id}): {formatted_message}")
logger.info(f"[MailingService] Отправлено уведомление {telegram_id} ({chat_id}): {formatted_message}")
except apihelper.ApiTelegramException as e:
# Попытка получить error_code и описание из исключения
error_code = None
description = None
try:
if hasattr(e, "result") and e.result:
error_code = e.result.get("error_code")
description = e.result.get("description")
except Exception:
pass
if error_code == 429:
logger.warning(f"[MailingService][tg message] Rate limit для {telegram_id} ({chat_id}), ждем и повторяем...")
if "429" in str(e):
logger.warning(f"[MailingService] Rate limit для {telegram_id} ({chat_id}), ждем и повторяем...")
await asyncio.sleep(1)
await self.send_message(chat_id, message, link)
elif error_code in (400, 403) and description and (
"user is deactivated" in description.lower() or
"bot was blocked by the user" in description.lower() or
"user not found" in description.lower()
):
logger.warning(f"[MailingService][tg message] Ошибка {error_code}: {description}. Пользователь {telegram_id} ({chat_id}) заблокировал бота или не существует. Отправка пропущена.")
# Не повторяем отправку
await self.send_message(chat_id, message)
else:
logger.error(f"[MailingService][tg message] Ошибка отправки сообщения {telegram_id} ({chat_id}): {e}")
logger.error(f"[MailingService] Ошибка отправки сообщения {telegram_id} ({chat_id}): {e}")
except Exception as e:
logger.error(f"[MailingService][tg message] Неожиданная ошибка отправки сообщения {telegram_id} ({chat_id}): {e}")
logger.error(f"[MailingService] Неожиданная ошибка отправки сообщения {telegram_id} ({chat_id}): {e}")
finally:
rate_limit_semaphore.release()

View File

@ -1,31 +1,19 @@
# parser.py
import re
# def parse_region_id(host: str) -> int | None:
# """
# Извлекает region_id из строки host.
# Формат: p<region><...>, например p18ecpapp01 → region_id = 18
#
# Returns:
# int | None: номер региона или None
# """
# if not host or not host.startswith("p"):
# return None
#
# match = re.match(r"^p(\d+)", host)
# if match:
# return int(match.group(1))
# return None
def parse_region_id(host: str) -> int | None:
"""
Извлекает region_id из строки host.
Формат: p<region><...>, например p18ecpapp01 region_id = 18
def parse_region_id(hostgroups: str | None) -> str:
if not hostgroups:
return "0"
Returns:
int | None: номер региона или None
"""
if not host or not host.startswith("p"):
return None
if hostgroups == "p00rtmis":
return "0"
parts = hostgroups.split("_")
if len(parts) >= 2:
return parts[1]
return "0"
match = re.match(r"^p(\d+)", host)
if match:
return int(match.group(1))
return None

View File

@ -1,17 +0,0 @@
from typing import Optional
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
def create_link_button(link: Optional[str]) -> Optional[InlineKeyboardMarkup]:
"""
Создаёт InlineKeyboardMarkup с кнопкой-ссылкой.
Если ссылка не передана, возвращает None.
"""
if not link:
return None
markup = InlineKeyboardMarkup()
button = InlineKeyboardButton(text="График", url=link)
markup.add(button)
return markup

View File

@ -1,6 +0,0 @@
def get_telegram_id_by_chat_id(flask_app, chat_id: int) -> str:
from app.models.users import Users # импорт здесь, чтобы избежать циклических зависимостей
with flask_app.app_context():
user = Users.query.filter_by(chat_id=chat_id).first()
return user.telegram_id if user else "unknown"

View File

@ -27,13 +27,14 @@ RABBITMQ_QUEUE = os.environ.get("RABBITMQ_QUEUE", "telegram_notifications")
RABBITMQ_NOTIFICATIONS_QUEUE = os.environ.get("RABBITMQ_NOTIFICATIONS_QUEUE", "notifications_queue")
RABBITMQ_VHOST = os.getenv("RABBITMQ_VHOST", "/")
RABBITMQ_VHOST_ENCODED = quote_plus(RABBITMQ_VHOST)
RABBITMQ_URL_FULL = (
f"amqp://{RABBITMQ_LOGIN}:{RABBITMQ_PASS}@{RABBITMQ_HOST}:{RABBITMQ_PORT}/{RABBITMQ_VHOST_ENCODED}"
)
# Mailing settings
MAILING_MAX_WORKERS = int(os.getenv("MAILING_MAX_WORKERS", "16"))
MAILING_RATE_LIMIT = int(os.getenv("MAILING_RATE_LIMIT", "25"))
MAILING_MAX_RETRIES = int(os.getenv("MAILING_MAX_RETRIES", "5"))
MAILING_MAX_WORKERS = int(os.environ.get("MAILING_MAX_WORKERS", "16"))
MAILING_RATE_LIMIT = int(os.environ.get("MAILING_RATE_LIMIT", "25"))
# Настройки Flask-LDAP3-login
LDAP_HOST = os.getenv('LDAP_HOST', 'localhost')
LDAP_PORT = int(os.getenv('LDAP_PORT', 389))