Compare commits
2 commits
6d530c9d97
...
fca335423e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fca335423e | ||
|
|
8b88344fac |
5 changed files with 79 additions and 58 deletions
|
|
@ -130,6 +130,7 @@ async def cmd_legal_sync(message: Message):
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command('admin'))
|
@router.message(Command('admin'))
|
||||||
|
@router.message(F.text.in_(['⚙️ Управление', '⚙️ Админка', '⚙️ АДМИНКА']))
|
||||||
@admin_only
|
@admin_only
|
||||||
async def cmd_admin(message: Message):
|
async def cmd_admin(message: Message):
|
||||||
"""Админ-меню (только в личке!)"""
|
"""Админ-меню (только в личке!)"""
|
||||||
|
|
@ -150,7 +151,7 @@ async def cmd_admin(message: Message):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.message(F.text == '📊 Статистика')
|
@router.message(F.text.in_(['📊 Статистика', '/stats']))
|
||||||
@admin_only
|
@admin_only
|
||||||
async def msg_stats_button(message: Message):
|
async def msg_stats_button(message: Message):
|
||||||
"""Кнопка Статистика"""
|
"""Кнопка Статистика"""
|
||||||
|
|
@ -162,19 +163,19 @@ async def msg_stats_button(message: Message):
|
||||||
await message.answer(text, parse_mode='HTML')
|
await message.answer(text, parse_mode='HTML')
|
||||||
|
|
||||||
|
|
||||||
@router.message(F.text == '🕵️ Шпионы')
|
@router.message(F.text.in_(['🛡️ Фильтр посторонних', '🕵️ Шпионы', '/spies']))
|
||||||
@admin_only
|
@admin_only
|
||||||
async def msg_spies_button(message: Message):
|
async def msg_spies_button(message: Message):
|
||||||
"""Кнопка Шпионы"""
|
"""Кнопка Шпионы / Фильтр посторонних"""
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
spy_detector = SpyDetector(session)
|
spy_detector = SpyDetector(session)
|
||||||
spies = await spy_detector.get_spies()
|
spies = await spy_detector.get_spies()
|
||||||
|
|
||||||
if not spies:
|
if not spies:
|
||||||
await message.answer('🟢 Шпионов не обнаружено')
|
await message.answer('🟢 Посторонних и шпионов не обнаружено')
|
||||||
return
|
return
|
||||||
|
|
||||||
text = '🚨 <b>ШПИОНЫ</b> 🚨\n\n'
|
text = '🚨 <b>ПОСТОРОННИЕ / ШПИОНЫ</b> 🚨\n\n'
|
||||||
for spy in spies:
|
for spy in spies:
|
||||||
name = spy.full_name or f'user{spy.user_id}'
|
name = spy.full_name or f'user{spy.user_id}'
|
||||||
apt = spy.apartment or '?'
|
apt = spy.apartment or '?'
|
||||||
|
|
@ -183,16 +184,16 @@ async def msg_spies_button(message: Message):
|
||||||
await message.answer(text, parse_mode='HTML')
|
await message.answer(text, parse_mode='HTML')
|
||||||
|
|
||||||
|
|
||||||
@router.message(F.text == '👥 Пользователи')
|
@router.message(F.text.in_(['👥 Список жителей', '👥 Пользователи', '/users']))
|
||||||
@admin_only
|
@admin_only
|
||||||
async def msg_users_button(message: Message):
|
async def msg_users_button(message: Message):
|
||||||
"""Кнопка Пользователи"""
|
"""Кнопка Пользователи / Список жителей"""
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
stmt = select(User).limit(50)
|
stmt = select(User).limit(50)
|
||||||
result = await session.execute(stmt)
|
result = await session.execute(stmt)
|
||||||
users = list(result.scalars().all())
|
users = list(result.scalars().all())
|
||||||
|
|
||||||
text = f'👥 <b>Пользователи</b> (50):\n\n'
|
text = f'👥 <b>Список жителей</b> (50):\n\n'
|
||||||
for user in users:
|
for user in users:
|
||||||
status = '✅' if user.verified else '⏳'
|
status = '✅' if user.verified else '⏳'
|
||||||
name = user.full_name or f'user{user.user_id}'
|
name = user.full_name or f'user{user.user_id}'
|
||||||
|
|
@ -204,7 +205,7 @@ async def msg_users_button(message: Message):
|
||||||
await message.answer(text, parse_mode='HTML')
|
await message.answer(text, parse_mode='HTML')
|
||||||
|
|
||||||
|
|
||||||
@router.message(F.text == '❓ Помощь')
|
@router.message(F.text.in_(['❓ Справка', '❓ Помощь', '/help']), lambda m: config.is_admin(m.from_user.id))
|
||||||
@admin_only
|
@admin_only
|
||||||
async def msg_help_button(message: Message):
|
async def msg_help_button(message: Message):
|
||||||
"""Кнопка Помощь - справка по командам"""
|
"""Кнопка Помощь - справка по командам"""
|
||||||
|
|
@ -699,6 +700,7 @@ async def cmd_spies(message: Message):
|
||||||
|
|
||||||
@router.message(Command('export'))
|
@router.message(Command('export'))
|
||||||
@router.message(Command('экспорт'))
|
@router.message(Command('экспорт'))
|
||||||
|
@router.message(F.text.in_(['📥 Выгрузка базы', '📥 Экспорт JSON']))
|
||||||
@admin_only
|
@admin_only
|
||||||
async def cmd_export(message: Message):
|
async def cmd_export(message: Message):
|
||||||
"""Экспорт данных"""
|
"""Экспорт данных"""
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,7 @@ async def cb_main_menu(callback: CallbackQuery):
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.message(Command('phones'))
|
@router.message(Command('phones'))
|
||||||
|
@router.message(F.text.in_(['📞 Важные телефоны', '📞 Телефоны', 'Телефоны']))
|
||||||
@router.callback_query(F.data == 'phones')
|
@router.callback_query(F.data == 'phones')
|
||||||
async def cb_phones(event):
|
async def cb_phones(event):
|
||||||
text = '📞 <b>ЭКСТРЕННЫЕ ТЕЛЕФОНЫ</b>\n\nВыберите категорию ниже:'
|
text = '📞 <b>ЭКСТРЕННЫЕ ТЕЛЕФОНЫ</b>\n\nВыберите категорию ниже:'
|
||||||
|
|
@ -117,7 +118,7 @@ async def cb_phones(event):
|
||||||
await safe_edit_or_reply(event, text, reply_markup=kb, parse_mode='HTML')
|
await safe_edit_or_reply(event, text, reply_markup=kb, parse_mode='HTML')
|
||||||
|
|
||||||
@router.message(Command('masters'))
|
@router.message(Command('masters'))
|
||||||
@router.message(F.text.lower() == 'мастера')
|
@router.message(F.text.in_(['👷 Мастера', 'Мастера', 'мастера']))
|
||||||
async def cmd_masters(message: Message):
|
async def cmd_masters(message: Message):
|
||||||
"""Выдача списка мастеров по команде /masters или тексту 'мастера'"""
|
"""Выдача списка мастеров по команде /masters или тексту 'мастера'"""
|
||||||
from database.models import Service
|
from database.models import Service
|
||||||
|
|
@ -334,6 +335,7 @@ class HelpFeedbackState(StatesGroup):
|
||||||
waiting_for_reply = State()
|
waiting_for_reply = State()
|
||||||
|
|
||||||
@router.message(Command('help'))
|
@router.message(Command('help'))
|
||||||
|
@router.message(F.text.in_(['❓ Справка', '❓ Помощь', 'Справка', 'Помощь']))
|
||||||
@router.callback_query(F.data == 'help')
|
@router.callback_query(F.data == 'help')
|
||||||
async def cb_help(event, state: FSMContext):
|
async def cb_help(event, state: FSMContext):
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
|
||||||
|
|
@ -14,53 +14,52 @@ def get_main_menu(verified: bool = False, is_ig: bool = False, is_admin: bool =
|
||||||
|
|
||||||
# СТРОКА 1: БАЗА (ВСЕГДА ТУТ)
|
# СТРОКА 1: БАЗА (ВСЕГДА ТУТ)
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(text='📞 Телефоны', callback_data='phones'),
|
InlineKeyboardButton(text='📞 Важные телефоны', callback_data='phones'),
|
||||||
InlineKeyboardButton(text='⚖️ Юрист (ЖК РФ)', callback_data='main_jurist')
|
InlineKeyboardButton(text='⚖️ Юрист (ЖК РФ)', callback_data='main_jurist')
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(text='🏢 О доме', callback_data='about_house')
|
InlineKeyboardButton(text='🏢 Паспорт дома', callback_data='about_house')
|
||||||
)
|
)
|
||||||
|
|
||||||
# СТРОКА 2-3: ДЛЯ ВЕРИФИЦИРОВАННЫХ (ФИКСИРОВАННОЕ МЕСТО)
|
# СТРОКА 2-3: ДЛЯ ВЕРИФИЦИРОВАННЫХ (ФИКСИРОВАННОЕ МЕСТО)
|
||||||
if verified:
|
if verified:
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(text='📅 График', callback_data='schedule'),
|
InlineKeyboardButton(text='⚠️ Отключения', callback_data='schedule'),
|
||||||
InlineKeyboardButton(text='💰 Платежи', callback_data='payments')
|
InlineKeyboardButton(text='💳 Оплата ЖКХ', callback_data='payments')
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(text='📋 Опросы', callback_data='polls'),
|
InlineKeyboardButton(text='🗳️ Голосования', callback_data='polls'),
|
||||||
InlineKeyboardButton(text='🎉 События', callback_data='events')
|
InlineKeyboardButton(text='🗓️ События дома', callback_data='events')
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(text='🏪 Маркет', callback_data='market'),
|
InlineKeyboardButton(text='📢 Объявления', callback_data='market'),
|
||||||
InlineKeyboardButton(text='🏆 Рейтинг', callback_data='achievements')
|
InlineKeyboardButton(text='🤝 Добрые соседи', callback_data='achievements')
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Для гостей кнопка верификации на видном месте
|
# Для гостей кнопка верификации на видном месте
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(text='🛡️ ВЕРИФИКАЦИЯ', callback_data='start_verification')
|
InlineKeyboardButton(text='🔑 Подтвердить квартиру', callback_data='start_verification')
|
||||||
)
|
)
|
||||||
|
|
||||||
# СТРОКА 4: ИНИЦИАТИВНАЯ ГРУППА И АДМИН
|
# СТРОКА 4: ИНИЦИАТИВНАЯ ГРУППА И АДМИН
|
||||||
if is_ig or is_admin:
|
if is_ig or is_admin:
|
||||||
buttons = []
|
buttons = []
|
||||||
if is_ig:
|
if is_ig:
|
||||||
buttons.append(InlineKeyboardButton(text='🎯 ТИМ-МЕНЮ', callback_data='ig_menu'))
|
buttons.append(InlineKeyboardButton(text='👥 Совет Дома', callback_data='ig_menu'))
|
||||||
if is_admin:
|
if is_admin:
|
||||||
buttons.append(InlineKeyboardButton(text='⚙️ АДМИНКА', callback_data='admin_menu'))
|
buttons.append(InlineKeyboardButton(text='⚙️ Управление', callback_data='admin_menu'))
|
||||||
builder.row(*buttons)
|
builder.row(*buttons)
|
||||||
|
|
||||||
# СТРОКА 5: СУПЕР-АДМИН (ТЫ)
|
# СТРОКА 5: СУПЕР-АДМИН (ПРЕДСЕДАТЕЛЬ)
|
||||||
if is_superadmin:
|
if is_superadmin:
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(text='👑 [ROOT: УПРАВЛЕНИЕ]', callback_data='superadmin_menu')
|
InlineKeyboardButton(text='🛡️ Панель председателя', callback_data='superadmin_menu')
|
||||||
)
|
)
|
||||||
|
|
||||||
# СТРОКА 6: СЛУЖЕБНОЕ (НИЗ)
|
# СТРОКА 6: СЛУЖЕБНОЕ (НИЗ)
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(text='❓ Помощь', callback_data='help'),
|
InlineKeyboardButton(text='❓ Справка', callback_data='help'),
|
||||||
InlineKeyboardButton(text='🌦️ Погода', callback_data='weather'),
|
InlineKeyboardButton(text='👤 Моя квартира', callback_data='my_profile')
|
||||||
InlineKeyboardButton(text='👤 Профиль', callback_data='my_profile')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return builder.as_markup()
|
return builder.as_markup()
|
||||||
|
|
@ -124,14 +123,14 @@ def get_admin_menu() -> InlineKeyboardMarkup:
|
||||||
|
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(text='📊 Статистика', callback_data='admin_stats'),
|
InlineKeyboardButton(text='📊 Статистика', callback_data='admin_stats'),
|
||||||
InlineKeyboardButton(text='🕵️ Шпионы', callback_data='admin_spies'),
|
InlineKeyboardButton(text='🛡️ Фильтр посторонних', callback_data='admin_spies'),
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(text='👥 Пользователи', callback_data='admin_users'),
|
InlineKeyboardButton(text='👥 Список жителей', callback_data='admin_users'),
|
||||||
InlineKeyboardButton(text='⏳ Непроверенные', callback_data='admin_unverified'),
|
InlineKeyboardButton(text='⏳ Ожидают проверки', callback_data='admin_unverified'),
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(text='📥 Экспорт JSON', callback_data='admin_export'),
|
InlineKeyboardButton(text='📥 Выгрузка базы', callback_data='admin_export'),
|
||||||
InlineKeyboardButton(text='📤 Рассылка', callback_data='admin_broadcast'),
|
InlineKeyboardButton(text='📤 Рассылка', callback_data='admin_broadcast'),
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ def get_reply_menu() -> ReplyKeyboardMarkup:
|
||||||
builder = ReplyKeyboardBuilder()
|
builder = ReplyKeyboardBuilder()
|
||||||
|
|
||||||
builder.row(
|
builder.row(
|
||||||
KeyboardButton(text='📞 Телефоны'),
|
KeyboardButton(text='📞 Важные телефоны'),
|
||||||
KeyboardButton(text='👷 Мастера'),
|
KeyboardButton(text='👷 Мастера'),
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
KeyboardButton(text='❓ Помощь'),
|
KeyboardButton(text='❓ Справка'),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Resize keyboard чтобы не занимал много места
|
# Resize keyboard чтобы не занимал много места
|
||||||
|
|
@ -29,12 +29,13 @@ def get_admin_reply_menu() -> ReplyKeyboardMarkup:
|
||||||
builder = ReplyKeyboardBuilder()
|
builder = ReplyKeyboardBuilder()
|
||||||
|
|
||||||
builder.row(
|
builder.row(
|
||||||
KeyboardButton(text='/admin'),
|
KeyboardButton(text='⚙️ Управление'),
|
||||||
KeyboardButton(text='/stats'),
|
KeyboardButton(text='📊 Статистика'),
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
KeyboardButton(text='/spies'),
|
KeyboardButton(text='🛡️ Фильтр посторонних'),
|
||||||
KeyboardButton(text='/export'),
|
KeyboardButton(text='📥 Выгрузка базы'),
|
||||||
)
|
)
|
||||||
|
|
||||||
return builder.as_markup(resize_keyboard=True)
|
return builder.as_markup(resize_keyboard=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class Scheduler:
|
||||||
|
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.scheduler = AsyncIOScheduler()
|
self.scheduler = AsyncIOScheduler(timezone='Europe/Ulyanovsk')
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Запуск планировщика"""
|
"""Запуск планировщика"""
|
||||||
|
|
@ -141,25 +141,24 @@ class Scheduler:
|
||||||
)
|
)
|
||||||
logger.info('✅ Добавлена задача ежедневного прогноза погоды для админа')
|
logger.info('✅ Добавлена задача ежедневного прогноза погоды для админа')
|
||||||
|
|
||||||
# Еженедельный прогноз в чат по понедельникам (в 09:00)
|
# [ОТКЛЮЧЕНО ПО ТЗ] Еженедельный прогноз в чат по понедельникам и четвергам отключен для группы дома
|
||||||
self.scheduler.add_job(
|
# Жильцы начали активнее общаться, прогноз погоды в общей группе больше не требуется.
|
||||||
self.monday_chat_weather,
|
# Личный прогноз для Александра (daily_admin_weather) выше остается активным.
|
||||||
CronTrigger.from_crontab('0 9 * * 1'), # Пн в 09:00
|
# self.scheduler.add_job(
|
||||||
id='monday_chat_weather',
|
# self.monday_chat_weather,
|
||||||
name='Прогноз погоды в чат (Понедельник)',
|
# CronTrigger.from_crontab('0 9 * * 1'),
|
||||||
replace_existing=True
|
# id='monday_chat_weather',
|
||||||
)
|
# name='Прогноз погоды в чат (Понедельник)',
|
||||||
logger.info('✅ Добавлена задача прогноза погоды в чат по понедельникам')
|
# replace_existing=True
|
||||||
|
# )
|
||||||
# Еженедельный прогноз в чат по четвергам (в 09:00)
|
# self.scheduler.add_job(
|
||||||
self.scheduler.add_job(
|
# self.thursday_chat_weather,
|
||||||
self.thursday_chat_weather,
|
# CronTrigger.from_crontab('0 9 * * 4'),
|
||||||
CronTrigger.from_crontab('0 9 * * 4'), # Чт в 09:00
|
# id='thursday_chat_weather',
|
||||||
id='thursday_chat_weather',
|
# name='Прогноз погоды в чат (Четверг)',
|
||||||
name='Прогноз погоды в чат (Четверг)',
|
# replace_existing=True
|
||||||
replace_existing=True
|
# )
|
||||||
)
|
logger.info('ℹ️ Прогноз погоды в общий чат дома отключен по требованию председателя')
|
||||||
logger.info('✅ Добавлена задача прогноза погоды в чат по четвергам')
|
|
||||||
|
|
||||||
# Еженедельный дайджест Александра (суббота в 08:00)
|
# Еженедельный дайджест Александра (суббота в 08:00)
|
||||||
self.scheduler.add_job(
|
self.scheduler.add_job(
|
||||||
|
|
@ -550,8 +549,17 @@ class Scheduler:
|
||||||
"""Прогноз в чат по понедельникам (на 3 дня)"""
|
"""Прогноз в чат по понедельникам (на 3 дня)"""
|
||||||
try:
|
try:
|
||||||
from services.weather import WeatherService
|
from services.weather import WeatherService
|
||||||
from config import ADMIN_CHAT_ID
|
from config import ADMIN_CHAT_ID, ADMIN_USER_ID
|
||||||
forecast = await WeatherService.get_3day_forecast()
|
forecast = await WeatherService.get_3day_forecast()
|
||||||
|
if not forecast or forecast.startswith("❌"):
|
||||||
|
logger.warning(f"Monday weather forecast failed: {forecast}")
|
||||||
|
await self.bot.send_message(
|
||||||
|
ADMIN_USER_ID,
|
||||||
|
f"⚠️ <b>Внимание:</b> Не удалось сгенерировать прогноз погоды на понедельник для общего чата:\n{forecast}",
|
||||||
|
parse_mode='HTML'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
await self.bot.send_message(
|
await self.bot.send_message(
|
||||||
ADMIN_CHAT_ID,
|
ADMIN_CHAT_ID,
|
||||||
forecast,
|
forecast,
|
||||||
|
|
@ -565,8 +573,17 @@ class Scheduler:
|
||||||
"""Прогноз в чат по четвергам (на остаток недели и выходные)"""
|
"""Прогноз в чат по четвергам (на остаток недели и выходные)"""
|
||||||
try:
|
try:
|
||||||
from services.weather import WeatherService
|
from services.weather import WeatherService
|
||||||
from config import ADMIN_CHAT_ID
|
from config import ADMIN_CHAT_ID, ADMIN_USER_ID
|
||||||
forecast = await WeatherService.get_thursday_weekend_broadcast()
|
forecast = await WeatherService.get_thursday_weekend_broadcast()
|
||||||
|
if not forecast or forecast.startswith("❌"):
|
||||||
|
logger.warning(f"Thursday weather forecast failed: {forecast}")
|
||||||
|
await self.bot.send_message(
|
||||||
|
ADMIN_USER_ID,
|
||||||
|
f"⚠️ <b>Внимание:</b> Не удалось сгенерировать прогноз погоды на четверг для общего чата:\n{forecast}",
|
||||||
|
parse_mode='HTML'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
await self.bot.send_message(
|
await self.bot.send_message(
|
||||||
ADMIN_CHAT_ID,
|
ADMIN_CHAT_ID,
|
||||||
forecast,
|
forecast,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue