diff --git a/data/broadcast_Gemini_Generated_Image_7v4paw7v4paw7v4p.png b/data/broadcast_Gemini_Generated_Image_7v4paw7v4paw7v4p.png
new file mode 100644
index 0000000..21854d1
Binary files /dev/null and b/data/broadcast_Gemini_Generated_Image_7v4paw7v4paw7v4p.png differ
diff --git a/data/service_images/masters_0d95ea9537e4480b82da5b475c34dfe2.png b/data/service_images/masters_0d95ea9537e4480b82da5b475c34dfe2.png
new file mode 100644
index 0000000..b4bf3a6
Binary files /dev/null and b/data/service_images/masters_0d95ea9537e4480b82da5b475c34dfe2.png differ
diff --git a/data/service_images/masters_715a0073539c410daae24f5b59c07b74.png b/data/service_images/masters_715a0073539c410daae24f5b59c07b74.png
new file mode 100644
index 0000000..b4bf3a6
Binary files /dev/null and b/data/service_images/masters_715a0073539c410daae24f5b59c07b74.png differ
diff --git a/handlers/users.py b/handlers/users.py
index 6206fad..2ac8cc9 100644
--- a/handlers/users.py
+++ b/handlers/users.py
@@ -206,8 +206,7 @@ async def cmd_help(message: Message):
📱 Доступные команды:
Для всех:
-📞 /телефон — Экстренные службы
-👷 /мастера — Сантехники, электрики
+📞 /телефон — Экстренные службы (включая мастеров)
👮 /участковый — Контакты полиции
📜 /правила — Правила чата
❓ /помощь — Эта справка
diff --git a/keyboards/inline.py b/keyboards/inline.py
index de74e59..b352cb1 100644
--- a/keyboards/inline.py
+++ b/keyboards/inline.py
@@ -39,10 +39,7 @@ def get_main_menu(verified: bool = False, is_ig: bool = False, is_admin: bool =
builder.row(
InlineKeyboardButton(text=f'{level_emoji} 📞 Экстренные телефоны', callback_data='phones'),
)
- builder.row(
- InlineKeyboardButton(text=f'{level_emoji} 👷 Мастера', callback_data='masters'),
- )
-
+
# Только для верифицированных
if verified:
builder.row(
@@ -207,7 +204,6 @@ def get_unverified_menu() -> InlineKeyboardMarkup:
InlineKeyboardButton(text=f'{emoji} 📞 Экстренные телефоны', callback_data='phones'),
)
builder.row(
- InlineKeyboardButton(text=f'{emoji} 👷 Мастера', callback_data='masters'),
InlineKeyboardButton(text=f'{emoji} 👮 Участковый', callback_data='police'),
)
builder.row(
diff --git a/keyboards/reply.py b/keyboards/reply.py
index 59d24d4..6518252 100644
--- a/keyboards/reply.py
+++ b/keyboards/reply.py
@@ -11,15 +11,14 @@ def get_reply_menu() -> ReplyKeyboardMarkup:
Используется редко, в основном для важных действий
"""
builder = ReplyKeyboardBuilder()
-
+
builder.row(
KeyboardButton(text='📞 Телефоны'),
- KeyboardButton(text='👷 Мастера'),
)
builder.row(
KeyboardButton(text='❓ Помощь'),
)
-
+
# Resize keyboard чтобы не занимал много места
return builder.as_markup(resize_keyboard=True, one_time_keyboard=False)
diff --git a/send_forum_capabilities.py b/send_forum_capabilities.py
new file mode 100644
index 0000000..8475b37
--- /dev/null
+++ b/send_forum_capabilities.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+"""
+Отправка подробного отчёта о возможностях рассылки бота
+"""
+import asyncio
+import sys
+from pathlib import Path
+
+BASE_DIR = Path(__file__).resolve().parent
+sys.path.insert(0, str(BASE_DIR))
+
+from aiogram import Bot
+from aiogram.client.default import DefaultBotProperties
+from aiogram.enums import ParseMode
+from aiogram.client.session.aiohttp import AiohttpSession
+
+from config import BOT_TOKEN, ADMIN_USER_ID, get_proxy_url
+
+async def send_report():
+ """Отправка отчёта админу"""
+ proxy_url = get_proxy_url()
+
+ if proxy_url:
+ session = AiohttpSession(proxy=proxy_url)
+ bot = Bot(token=BOT_TOKEN, session=session, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
+ else:
+ bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
+
+ message = """
+📊 АНАЛИЗ ВОЗМОЖНОСТЕЙ РАССЫЛКИ ДОМОВОГО БОТА
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+🎯 ОТВЕТЫ НА ТВОИ ВОПРОСЫ:
+
+✅ 1. МОЖЕТ ЛИ БОТ ПИСАТЬ В ОТДЕЛЬНУЮ ТЕМУ?
+
+Да! Бот уже настроен для работы с темами форума Telegram.
+
+📋 Настроенные темы:
+├ 1️⃣ Общий чат (ID: 1)
+├ 2️⃣ Важные объявления (ID: 6258)
+├ 3️⃣ Ссылки (ID: 6265)
+├ 4️⃣ Мемы (ID: 6262)
+├ 5️⃣ Для автовладельцев (ID: 6266)
+├ 6️⃣ Потеряшки (ID: 6263)
+├ 7️⃣ Собрания собственников (ID: 6261)
+├ 8️⃣ Вывоз КГМ (ID: 6264)
+├ 9️⃣ Переписка с УО (ID: 6260)
+└ 🔟 Важные телефоны (ID: 6259)
+
+💡 Как работает:
+Бот использует параметр message_thread_id при отправке:
+• send_message(chat_id=CHAT_ID, message_thread_id=TOPIC_ID)
+• Это позволяет писать в КОНКРЕТНУЮ тему!
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+✅ 2. МОЖЕТ ЛИ БОТ ПИСАТЬ В ОБЩИЙ ЧАТ?
+
+Да! Уже реализовано в нескольких местах:
+
+📤 Способы рассылки:
+
+1️⃣ Через админку в Telegram:
+ /admin → кнопка «📤 Рассылка»
+ → Отправляет в ОБЩУЮ ТЕМУ + всем жильцам в личку
+
+2️⃣ Через веб-панель (порт 8000):
+ http://localhost:8000/broadcast
+ → Выбор получателей:
+ • Все верифицированные + чат
+ • Только ИГ
+ • Только чат
+ → Отправка в ОБЩУЮ ТЕМУ чата
+
+3️⃣ Через команды:
+ • /ig_broadcast — рассылка только ИГ
+ • Рассылка админа — всем жильцам
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+🎭 ДЛЯ ТВОЕЙ ЗАДАЧИ (ЮМОР ПРО МУСОР + ДОНАТ):
+
+Вариант 1: Общий чат
+• Писать в тему «Общий чат» (ID: 1)
+• Увидят ВСЕ жильцы
+• Подходит для доната на суд
+
+Вариант 2: Тема Мемы
+• Писать в тему «Мемы» (ID: 6262)
+• Идеально для юмора про мусор!
+
+Вариант 3: Тема Объявления
+• Писать в тему «Важные объявления» (ID: 6258)
+• Для серьёзных постов про донат
+
+Вариант 4: Тема Собрания
+• Писать в тему «Собрания собственников» (ID: 6261)
+• Для важных решений по УК
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+🔧 КАК ЭТО БУДЕТ РАБОТАТЬ:
+
+Создам скрипт, который:
+✅ Пишет пост в КОНКРЕТНУЮ тему
+✅ Использует прокси (VPN Германия)
+✅ Форматирует текст с emoji
+✅ Может добавить картинку
+✅ Постит серию постов с интервалом
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+🚀 ГОТОВО К РАБОТЕ!
+
+Бот уже имеет ВСЕ необходимые функции:
+✅ Отправка в конкретную тему
+✅ Отправка в общий чат
+✅ Рассылка всем жильцам
+✅ Поддержка фото
+✅ Работа через прокси
+
+Нужно только создать скрипт для постинга серии сообщений!
+ """
+
+ try:
+ await bot.send_message(ADMIN_USER_ID, message)
+ print("✅ Подробный отчёт отправлен!")
+ except Exception as e:
+ print(f"❌ Ошибка: {e}")
+ finally:
+ await bot.session.close()
+
+if __name__ == '__main__':
+ asyncio.run(send_report())
diff --git a/send_scheduler_analysis.py b/send_scheduler_analysis.py
new file mode 100644
index 0000000..0f588c6
--- /dev/null
+++ b/send_scheduler_analysis.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python3
+"""
+Отправка анализа возможности планировщика постов
+"""
+import asyncio
+import sys
+from pathlib import Path
+
+BASE_DIR = Path(__file__).resolve().parent
+sys.path.insert(0, str(BASE_DIR))
+
+from aiogram import Bot
+from aiogram.client.default import DefaultBotProperties
+from aiogram.enums import ParseMode
+from aiogram.client.session.aiohttp import AiohttpSession
+
+from config import BOT_TOKEN, ADMIN_USER_ID, get_proxy_url
+
+async def send_analysis():
+ """Отправка анализа планировщика постов"""
+ proxy_url = get_proxy_url()
+
+ if proxy_url:
+ session = AiohttpSession(proxy=proxy_url)
+ bot = Bot(token=BOT_TOKEN, session=session, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
+ else:
+ bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
+
+ message = """
+📅 АНАЛИЗ: ПЛАНИРОВЩИК ПОСТОВ ДЛЯ ВЕБ-ПАНЕЛИ
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+🎯 МОЖЕМ ЛИ РЕАЛИЗОВАТЬ?
+
+✅ ДА! Полностью реализуемо!
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+📋 ЧТО УЖЕ ЕСТЬ В КОДЕ:
+
+✅ 1. Планировщик (Scheduler)
+ • Файл: services/scheduler.py
+ • Использует APScheduler
+ • Уже работает с cron-задачами
+ • Запускается при старте бота
+
+✅ 2. Веб-панель (FastAPI)
+ • Файл: web/app.py (911 строк)
+ • Есть страница /broadcast
+ • Уже умеет: текст + фото
+ • Уже есть выбор получателей
+
+✅ 3. База данных (SQLAlchemy)
+ • Есть модели для всего
+ • Нужно добавить: ScheduledPost
+ • Хранить: текст, фото, тему, время
+
+✅ 4. Темы форума
+ • Настроены 10 тем
+ • message_thread_id работает
+ • config.get_topic_id('topic_name')
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+🔧 ЧТО НУЖНО ДОБАВИТЬ:
+
+1️⃣ Новая модель в БД:
+ ScheduledPost
+ • id
+ • text (текст поста)
+ • photo_file_id (опционально)
+ • topic_id (тема: 1, 6258, 6262...)
+ • topic_name ('general', 'memes'...)
+ • scheduled_time (когда отправить)
+ • status ('pending', 'sent', 'failed')
+ • created_at (когда создан)
+ • error_message (если ошибка)
+
+2️⃣ Новая страница в веб-панели:
+ /scheduled_posts
+ • Текстовое поле (с HTML поддержкой)
+ • Загрузка фото
+ • Выпадающий список тем
+ • Датапикер (календарь)
+ • Время (часы:минуты)
+ • Кнопка "Запланировать"
+ • Таблица запланированных
+ • Кнопки: удалить, редактировать
+
+3️⃣ API endpoints:
+ • GET /scheduled_posts - страница
+ • POST /api/scheduled_posts/create
+ • POST /api/scheduled_posts/cancel
+ • GET /api/scheduled_posts/list
+ • DELETE /api/scheduled_posts/{id}
+
+4️⃣ Фоновая задача в Scheduler:
+ • Проверка каждую минуту
+ • SELECT * FROM scheduled_posts
+ WHERE scheduled_time <= NOW()
+ AND status = 'pending'
+ • Отправка в нужную тему
+ • Обновление status = 'sent'
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+🎨 КАК БУДЕТ ВЫГЛЯДЕТЬ:
+
+Форма создания поста:
+
+📝 Текст: [большое поле]
+📷 Фото: [выбрать файл]
+📂 Тема: [выпадающий список ▼]
+ • Общий чат
+ • Мемы
+ • Объявления
+ • Собрания
+ • ...
+📅 Дата: [🗓️ 2026-04-07]
+⏰ Время: [14:30]
+
+[✅ Запланировать пост]
+
+Таблица запланированных:
+
+| # | Тема | Когда | Статус | Действия |
+|---|------|-------|--------|----------|
+| 1 | Мемы | 07.04 10:00 | ⏳ | ❌ ✏️ |
+| 2 | Общий | 08.04 15:00 | ⏳ | ❌ ✏️ |
+| 3 | КГМ | 09.04 09:00 | ✅ | - |
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+🛡️ БЕЗОПАСНОСТЬ (НЕ ИСПОРТИМ ТЕКУЩЕЕ):
+
+✅ Изолированные изменения:
+ • Новая модель - отдельная таблица
+ • Новый endpoint - новый URL
+ • Новая страница - новый файл HTML
+ • Новая задача в scheduler - отдельный job
+
+✅ Миграция БД:
+ • ALTER TABLE (добавление таблицы)
+ • Не меняем существующие таблицы
+ • Без риска для текущих данных
+
+✅ Тестирование:
+ • Можно тестировать на одном посте
+ • Отмена без последствий
+ • Логи всех действий
+
+✅ Откат:
+ • Если что-то не так - удаляем таблицу
+ • Новый код не трогает старый
+ • Старый функционал не изменится
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+🚀 ПЛАН РЕАЛИЗАЦИИ:
+
+Шаг 1: Добавить модель ScheduledPost в БД
+Шаг 2: Создать страницу /scheduled_posts
+Шаг 3: Создать API endpoints
+Шаг 4: Добавить задачу в Scheduler
+Шаг 5: Тестирование на 1-2 постах
+Шаг 6: Коммит в Gitea
+
+⚠️ ВАЖНО:
+Ни один существующий файл НЕ будет изменён критично!
+Только добавления нового кода.
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+💡 СЦЕНАРИЙ ИСПОЛЬЗОВАНИЯ:
+
+1. Заходишь в веб-панель (порт 8000)
+2. Кликаешь "📅 Запланированные посты"
+3. Заполняешь форму:
+ • Текст про юмор/мусор
+ • Прикладываешь картинку
+ • Выбираешь тему "Мемы"
+ • Ставишь: 07.04.2026 10:00
+4. Жмёшь "Запланировать"
+5. Повторяешь для 5-6 постов
+6. Бот сам отправит их по расписанию!
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+✅ ГОТОВО К РЕАЛИЗАЦИИ!
+
+Можем начинать? Никакой текущий функционал не пострадает!
+ """
+
+ try:
+ await bot.send_message(ADMIN_USER_ID, message)
+ print("✅ Анализ планировщика отправлен!")
+ except Exception as e:
+ print(f"❌ Ошибка: {e}")
+ finally:
+ await bot.session.close()
+
+if __name__ == '__main__':
+ asyncio.run(send_analysis())
diff --git a/send_status_notification.py b/send_status_notification.py
new file mode 100644
index 0000000..3f7f57d
--- /dev/null
+++ b/send_status_notification.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+"""
+Отправка уведомления через Домовой Бот
+"""
+import asyncio
+import sys
+from pathlib import Path
+
+# Добавляем путь к проекту
+BASE_DIR = Path(__file__).resolve().parent
+sys.path.insert(0, str(BASE_DIR))
+
+from aiogram import Bot
+from aiogram.client.default import DefaultBotProperties
+from aiogram.enums import ParseMode
+from aiogram.client.session.aiohttp import AiohttpSession
+
+from config import BOT_TOKEN, ADMIN_USER_ID, get_proxy_url
+
+async def send_notification():
+ """Отправка уведомления админу"""
+ proxy_url = get_proxy_url()
+
+ if proxy_url:
+ session = AiohttpSession(proxy=proxy_url)
+ bot = Bot(token=BOT_TOKEN, session=session, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
+ else:
+ bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
+
+ message = """
+✅ Домовой Бот — Статус системы
+
+🟢 VPN (Xray): РАБОТАЕТ
+├ Сервер: 🇩🇪 Германия (138.124.228.10)
+├ Протокол: VLESS + Reality
+├ SOCKS5: 127.0.0.1:10808 ✅
+└ HTTP: 127.0.0.1:10809 ✅
+
+🤖 Бот: ГОТОВ К РАБОТЕ
+├ Версия: v2.1
+├ Прокси: подключён
+└ Веб-интерфейс: порт 8000
+
+📋 Доступные функции:
+• 📊 Управление жильцами
+• 📢 Доска объявлений
+• 📅 Календарь событий
+• 📝 Опросы и голосования
+• 🏆 Рейтинг и достижения
+• 🔔 Напоминания о платежах
+• 🛡️ Анти-спам система
+• 📱 Веб-админка
+
+🎯 Готов к публикации постов!
+
+Система работает стабильно ✓
+ """
+
+ try:
+ await bot.send_message(ADMIN_USER_ID, message)
+ print("✅ Уведомление отправлено!")
+ except Exception as e:
+ print(f"❌ Ошибка: {e}")
+ finally:
+ await session.close() if proxy_url else await bot.session.close()
+
+if __name__ == '__main__':
+ asyncio.run(send_notification())
diff --git a/web/app.py b/web/app.py
index 348ccc0..0594ac7 100644
--- a/web/app.py
+++ b/web/app.py
@@ -10,6 +10,7 @@ from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
+from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, update, delete, desc
from datetime import datetime, timedelta
@@ -18,13 +19,23 @@ import config
from database.db import AsyncSessionLocal
from database.models import (
- User, Message, Poll, Ad, PaymentReminder, Schedule,
+ User, Message, Poll, Ad, PaymentReminder, Schedule,
VerificationRequest, Service, Announcement, Event
)
logger = logging.getLogger(__name__)
app = FastAPI(title="Домовой Бот - MEGA Admin Panel", version="3.0.0")
+
+# Добавляем CORS middleware
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"], # Разрешаем все origins (для локального использования)
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
templates = Jinja2Templates(directory="web/templates")
app.mount("/static", StaticFiles(directory="web/static"), name="static")
app.mount("/static/service_images", StaticFiles(directory="data/service_images"), name="service_images")
@@ -538,6 +549,9 @@ async def api_send_broadcast(
from pathlib import Path
from services.initiative_group import InitiativeGroupService
+ # Логируем начало рассылки
+ logger.info(f"📬 НАЧАЛО РАССЫЛКИ: получатели={recipients}, текст_len={len(text) if text else 0}")
+
photo_id = None
# Если есть картинка — загружаем
@@ -650,6 +664,9 @@ async def api_send_broadcast(
except Exception as e:
logger.error(f"Ошибка логирования рассылки: {e}")
+ # Логируем результат
+ logger.info(f"✅ РАССЫЛКА ЗАВЕРШЕНА: успешно={success_count}, ошибок={error_count}, получатели={recipients}")
+
return JSONResponse({
"success": True,
"message": f"Отправлено: {success_count} (в чат: {'✅' if chat_sent else '❌'})",