📌 snapshot before scheduled posts feature - v1.2 prep

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Admin 2026-04-05 15:13:37 +04:00
parent 1e61eca60c
commit 1825d26230
10 changed files with 430 additions and 11 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View file

@ -206,8 +206,7 @@ async def cmd_help(message: Message):
<b>📱 Доступные команды:</b> <b>📱 Доступные команды:</b>
<b>Для всех:</b> <b>Для всех:</b>
📞 /телефон Экстренные службы 📞 /телефон Экстренные службы (включая мастеров)
👷 /мастера Сантехники, электрики
👮 /участковый Контакты полиции 👮 /участковый Контакты полиции
📜 /правила Правила чата 📜 /правила Правила чата
/помощь Эта справка /помощь Эта справка

View file

@ -39,10 +39,7 @@ def get_main_menu(verified: bool = False, is_ig: bool = False, is_admin: bool =
builder.row( builder.row(
InlineKeyboardButton(text=f'{level_emoji} 📞 Экстренные телефоны', callback_data='phones'), InlineKeyboardButton(text=f'{level_emoji} 📞 Экстренные телефоны', callback_data='phones'),
) )
builder.row(
InlineKeyboardButton(text=f'{level_emoji} 👷 Мастера', callback_data='masters'),
)
# Только для верифицированных # Только для верифицированных
if verified: if verified:
builder.row( builder.row(
@ -207,7 +204,6 @@ def get_unverified_menu() -> InlineKeyboardMarkup:
InlineKeyboardButton(text=f'{emoji} 📞 Экстренные телефоны', callback_data='phones'), InlineKeyboardButton(text=f'{emoji} 📞 Экстренные телефоны', callback_data='phones'),
) )
builder.row( builder.row(
InlineKeyboardButton(text=f'{emoji} 👷 Мастера', callback_data='masters'),
InlineKeyboardButton(text=f'{emoji} 👮 Участковый', callback_data='police'), InlineKeyboardButton(text=f'{emoji} 👮 Участковый', callback_data='police'),
) )
builder.row( builder.row(

View file

@ -11,15 +11,14 @@ def get_reply_menu() -> ReplyKeyboardMarkup:
Используется редко, в основном для важных действий Используется редко, в основном для важных действий
""" """
builder = ReplyKeyboardBuilder() builder = ReplyKeyboardBuilder()
builder.row( builder.row(
KeyboardButton(text='📞 Телефоны'), KeyboardButton(text='📞 Телефоны'),
KeyboardButton(text='👷 Мастера'),
) )
builder.row( builder.row(
KeyboardButton(text='❓ Помощь'), KeyboardButton(text='❓ Помощь'),
) )
# Resize keyboard чтобы не занимал много места # Resize keyboard чтобы не занимал много места
return builder.as_markup(resize_keyboard=True, one_time_keyboard=False) return builder.as_markup(resize_keyboard=True, one_time_keyboard=False)

136
send_forum_capabilities.py Normal file
View file

@ -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 = """
📊 <b>АНАЛИЗ ВОЗМОЖНОСТЕЙ РАССЫЛКИ ДОМОВОГО БОТА</b>
🎯 <b>ОТВЕТЫ НА ТВОИ ВОПРОСЫ:</b>
<b>1. МОЖЕТ ЛИ БОТ ПИСАТЬ В ОТДЕЛЬНУЮ ТЕМУ?</b>
Да! Бот уже настроен для работы с темами форума Telegram.
📋 <b>Настроенные темы:</b>
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)
💡 <b>Как работает:</b>
Бот использует параметр message_thread_id при отправке:
send_message(chat_id=CHAT_ID, message_thread_id=TOPIC_ID)
Это позволяет писать в КОНКРЕТНУЮ тему!
<b>2. МОЖЕТ ЛИ БОТ ПИСАТЬ В ОБЩИЙ ЧАТ?</b>
Да! Уже реализовано в нескольких местах:
📤 <b>Способы рассылки:</b>
<b>1 Через админку в Telegram:</b>
/admin кнопка «📤 Рассылка»
Отправляет в ОБЩУЮ ТЕМУ + всем жильцам в личку
<b>2 Через веб-панель (порт 8000):</b>
http://localhost:8000/broadcast
Выбор получателей:
Все верифицированные + чат
Только ИГ
Только чат
Отправка в ОБЩУЮ ТЕМУ чата
<b>3 Через команды:</b>
/ig_broadcast рассылка только ИГ
Рассылка админа всем жильцам
🎭 <b>ДЛЯ ТВОЕЙ ЗАДАЧИ (ЮМОР ПРО МУСОР + ДОНАТ):</b>
<b>Вариант 1: Общий чат</b>
Писать в тему «Общий чат» (ID: 1)
Увидят ВСЕ жильцы
Подходит для доната на суд
<b>Вариант 2: Тема Мемы</b>
Писать в тему «Мемы» (ID: 6262)
Идеально для юмора про мусор!
<b>Вариант 3: Тема Объявления</b>
Писать в тему «Важные объявления» (ID: 6258)
Для серьёзных постов про донат
<b>Вариант 4: Тема Собрания</b>
Писать в тему «Собрания собственников» (ID: 6261)
Для важных решений по УК
🔧 <b>КАК ЭТО БУДЕТ РАБОТАТЬ:</b>
Создам скрипт, который:
Пишет пост в КОНКРЕТНУЮ тему
Использует прокси (VPN Германия)
Форматирует текст с emoji
Может добавить картинку
Постит серию постов с интервалом
🚀 <b>ГОТОВО К РАБОТЕ!</b>
Бот уже имеет ВСЕ необходимые функции:
Отправка в конкретную тему
Отправка в общий чат
Рассылка всем жильцам
Поддержка фото
Работа через прокси
<i>Нужно только создать скрипт для постинга серии сообщений!</i>
"""
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())

204
send_scheduler_analysis.py Normal file
View file

@ -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 = """
📅 <b>АНАЛИЗ: ПЛАНИРОВЩИК ПОСТОВ ДЛЯ ВЕБ-ПАНЕЛИ</b>
🎯 <b>МОЖЕМ ЛИ РЕАЛИЗОВАТЬ?</b>
<b>ДА! Полностью реализуемо!</b>
📋 <b>ЧТО УЖЕ ЕСТЬ В КОДЕ:</b>
<b>1. Планировщик (Scheduler)</b>
Файл: services/scheduler.py
Использует APScheduler
Уже работает с cron-задачами
Запускается при старте бота
<b>2. Веб-панель (FastAPI)</b>
Файл: web/app.py (911 строк)
Есть страница /broadcast
Уже умеет: текст + фото
Уже есть выбор получателей
<b>3. База данных (SQLAlchemy)</b>
Есть модели для всего
Нужно добавить: ScheduledPost
Хранить: текст, фото, тему, время
<b>4. Темы форума</b>
Настроены 10 тем
message_thread_id работает
config.get_topic_id('topic_name')
🔧 <b>ЧТО НУЖНО ДОБАВИТЬ:</b>
<b>1 Новая модель в БД:</b>
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 (если ошибка)
<b>2 Новая страница в веб-панели:</b>
/scheduled_posts
Текстовое поле (с HTML поддержкой)
Загрузка фото
Выпадающий список тем
Датапикер (календарь)
Время (часы:минуты)
Кнопка "Запланировать"
Таблица запланированных
Кнопки: удалить, редактировать
<b>3 API endpoints:</b>
GET /scheduled_posts - страница
POST /api/scheduled_posts/create
POST /api/scheduled_posts/cancel
GET /api/scheduled_posts/list
DELETE /api/scheduled_posts/{id}
<b>4 Фоновая задача в Scheduler:</b>
Проверка каждую минуту
SELECT * FROM scheduled_posts
WHERE scheduled_time &lt;= NOW()
AND status = 'pending'
Отправка в нужную тему
Обновление status = 'sent'
🎨 <b>КАК БУДЕТ ВЫГЛЯДЕТЬ:</b>
<b>Форма создания поста:</b>
📝 Текст: [большое поле]
📷 Фото: [выбрать файл]
📂 Тема: [выпадающий список ]
Общий чат
Мемы
Объявления
Собрания
...
📅 Дата: [🗓 2026-04-07]
Время: [14:30]
[ Запланировать пост]
<b>Таблица запланированных:</b>
| # | Тема | Когда | Статус | Действия |
|---|------|-------|--------|----------|
| 1 | Мемы | 07.04 10:00 | | |
| 2 | Общий | 08.04 15:00 | | |
| 3 | КГМ | 09.04 09:00 | | - |
🛡 <b>БЕЗОПАСНОСТЬ (НЕ ИСПОРТИМ ТЕКУЩЕЕ):</b>
<b>Изолированные изменения:</b>
Новая модель - отдельная таблица
Новый endpoint - новый URL
Новая страница - новый файл HTML
Новая задача в scheduler - отдельный job
<b>Миграция БД:</b>
ALTER TABLE (добавление таблицы)
Не меняем существующие таблицы
Без риска для текущих данных
<b>Тестирование:</b>
Можно тестировать на одном посте
Отмена без последствий
Логи всех действий
<b>Откат:</b>
Если что-то не так - удаляем таблицу
Новый код не трогает старый
Старый функционал не изменится
🚀 <b>ПЛАН РЕАЛИЗАЦИИ:</b>
<b>Шаг 1:</b> Добавить модель ScheduledPost в БД
<b>Шаг 2:</b> Создать страницу /scheduled_posts
<b>Шаг 3:</b> Создать API endpoints
<b>Шаг 4:</b> Добавить задачу в Scheduler
<b>Шаг 5:</b> Тестирование на 1-2 постах
<b>Шаг 6:</b> Коммит в Gitea
<b> ВАЖНО:</b>
Ни один существующий файл НЕ будет изменён критично!
Только добавления нового кода.
💡 <b>СЦЕНАРИЙ ИСПОЛЬЗОВАНИЯ:</b>
1. Заходишь в веб-панель (порт 8000)
2. Кликаешь "📅 Запланированные посты"
3. Заполняешь форму:
Текст про юмор/мусор
Прикладываешь картинку
Выбираешь тему "Мемы"
Ставишь: 07.04.2026 10:00
4. Жмёшь "Запланировать"
5. Повторяешь для 5-6 постов
6. Бот сам отправит их по расписанию!
<b>ГОТОВО К РЕАЛИЗАЦИИ!</b>
<i>Можем начинать? Никакой текущий функционал не пострадает!</i>
"""
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())

View file

@ -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 = """
<b>Домовой Бот Статус системы</b>
🟢 <b>VPN (Xray):</b> РАБОТАЕТ
Сервер: 🇩🇪 Германия (138.124.228.10)
Протокол: VLESS + Reality
SOCKS5: 127.0.0.1:10808
HTTP: 127.0.0.1:10809
🤖 <b>Бот:</b> ГОТОВ К РАБОТЕ
Версия: v2.1
Прокси: подключён
Веб-интерфейс: порт 8000
📋 <b>Доступные функции:</b>
📊 Управление жильцами
📢 Доска объявлений
📅 Календарь событий
📝 Опросы и голосования
🏆 Рейтинг и достижения
🔔 Напоминания о платежах
🛡 Анти-спам система
📱 Веб-админка
🎯 <b>Готов к публикации постов!</b>
<i>Система работает стабильно </i>
"""
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())

View file

@ -10,6 +10,7 @@ from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, update, delete, desc from sqlalchemy import select, func, update, delete, desc
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -18,13 +19,23 @@ import config
from database.db import AsyncSessionLocal from database.db import AsyncSessionLocal
from database.models import ( from database.models import (
User, Message, Poll, Ad, PaymentReminder, Schedule, User, Message, Poll, Ad, PaymentReminder, Schedule,
VerificationRequest, Service, Announcement, Event VerificationRequest, Service, Announcement, Event
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
app = FastAPI(title="Домовой Бот - MEGA Admin Panel", version="3.0.0") 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") templates = Jinja2Templates(directory="web/templates")
app.mount("/static", StaticFiles(directory="web/static"), name="static") app.mount("/static", StaticFiles(directory="web/static"), name="static")
app.mount("/static/service_images", StaticFiles(directory="data/service_images"), name="service_images") 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 pathlib import Path
from services.initiative_group import InitiativeGroupService from services.initiative_group import InitiativeGroupService
# Логируем начало рассылки
logger.info(f"📬 НАЧАЛО РАССЫЛКИ: получатели={recipients}, текст_len={len(text) if text else 0}")
photo_id = None photo_id = None
# Если есть картинка — загружаем # Если есть картинка — загружаем
@ -650,6 +664,9 @@ async def api_send_broadcast(
except Exception as e: except Exception as e:
logger.error(f"Ошибка логирования рассылки: {e}") logger.error(f"Ошибка логирования рассылки: {e}")
# Логируем результат
logger.info(f"✅ РАССЫЛКА ЗАВЕРШЕНА: успешно={success_count}, ошибок={error_count}, получатели={recipients}")
return JSONResponse({ return JSONResponse({
"success": True, "success": True,
"message": f"Отправлено: {success_count} (в чат: {'' if chat_sent else ''})", "message": f"Отправлено: {success_count} (в чат: {'' if chat_sent else ''})",