Изменение БД, добавление регистрации, добавление сложной рассылки, кнопки для СР

This commit is contained in:
ada-dmitry
2025-04-18 20:41:45 +03:00
parent aa82634f75
commit e1063b669f
6 changed files with 166 additions and 22 deletions
+1
View File
@@ -2,6 +2,7 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.idea/dataSources" />
<excludeFolder url="file://$MODULE_DIR$/.venv" /> <excludeFolder url="file://$MODULE_DIR$/.venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.13 (vc_bot)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.13 (vc_bot)" jdkType="Python SDK" />
+80
View File
@@ -0,0 +1,80 @@
from aiogram import Router, F, Bot
from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import StatesGroup, State
from sqlalchemy import select
from database import async_session
from models import User
from uuid import uuid4
import os
router = Router()
admin_ids = list(map(int, os.getenv("ADMINS", "").split(",")))
# Храним рассылки
adv_memory = {} # {adv_id: {"text": str, "is_active": bool}}
current_adv_id = None
# Состояния для FSM
class AdvForm(StatesGroup):
short = State()
full = State()
@router.message(F.text == "/send_adv")
async def start_adv(message: Message, state: FSMContext):
if message.from_user.id not in admin_ids:
return await message.answer("⛔ Доступ запрещён.")
await state.set_state(AdvForm.short)
await message.answer("Введите краткую форму сообщения:")
@router.message(AdvForm.short)
async def get_short_text(message: Message, state: FSMContext):
await state.update_data(short=message.text)
await state.set_state(AdvForm.full)
await message.answer("Теперь введите полную форму сообщения:")
@router.message(AdvForm.full)
async def get_full_text_and_send(message: Message, state: FSMContext, bot: Bot):
global current_adv_id, adv_memory
data = await state.get_data()
short_text = data["short"]
full_text = message.text
# Генерируем уникальный ID рассылки
adv_id = str(uuid4())
# Деактивируем старые рассылки
for adv in adv_memory.values():
adv["is_active"] = False
# Сохраняем новую рассылку
adv_memory[adv_id] = {
"text": full_text,
"is_active": True
}
current_adv_id = adv_id
# Клавиатура
kb = InlineKeyboardMarkup(inline_keyboard=[
[
InlineKeyboardButton(text="👍 Интересно", callback_data=f"adv:{adv_id}"),
InlineKeyboardButton(text="👎 Не интересно", callback_data="adv:no")
]
])
# Рассылка краткой формы
async with async_session() as session:
result = await session.execute(select(User.user_id).where(User.is_subscribed == True))
users = result.scalars().all()
sent = 0
for uid in users:
try:
await bot.send_message(uid, short_text, reply_markup=kb)
sent += 1
except:
continue
await message.answer(f"📬 Рассылка завершена. Отправлено: {sent}")
await state.clear()
+25
View File
@@ -0,0 +1,25 @@
from aiogram import Router, F
from aiogram.types import CallbackQuery
from handlers.admin_adv import adv_memory
router = Router()
@router.callback_query(F.data.startswith("adv:"))
async def handle_adv_click(callback: CallbackQuery):
adv_id = callback.data.split(":")[1]
adv = adv_memory.get(adv_id)
if not adv:
await callback.answer("❌ Сообщение не найдено", show_alert=True)
return
if not adv["is_active"]:
await callback.answer("⏰ Срок действия сообщения истёк", show_alert=True)
return
await callback.message.answer(adv["text"])
await callback.answer("Вот подробности 👇")
@router.callback_query(F.data == "adv:no")
async def handle_not_interested(callback: CallbackQuery):
await callback.answer("Спасибо за обратную связь!")
+44 -18
View File
@@ -1,5 +1,7 @@
from aiogram import Router, F from aiogram import Router, F
from aiogram.fsm.context import FSMContext
from aiogram.types import Message, CallbackQuery from aiogram.types import Message, CallbackQuery
from aiogram.fsm.state import State, StatesGroup
from sqlalchemy import select, insert, update from sqlalchemy import select, insert, update
from models import User from models import User
from database import async_session from database import async_session
@@ -7,27 +9,51 @@ from keyboards import get_subscription_keyboard
router = Router() router = Router()
# Машина состояний
class Register(StatesGroup):
waiting_for_fullname = State()
waiting_for_study_group = State()
@router.message(F.text == "/start") @router.message(F.text == "/start")
async def start_handler(message: Message): async def start_handler(message: Message, state: FSMContext):
async with async_session() as session: user_id = message.from_user.id
result = await session.execute(select(User).where(User.user_id == message.from_user.id)) async with async_session() as session:
result = await session.execute(select(User).where(User.user_id == user_id))
user = result.scalar_one_or_none() user = result.scalar_one_or_none()
if not user:
await session.execute(insert(User).values(user_id=message.from_user.id))
await session.commit()
text = "Добро пожаловать! Управляйте подпиской:"
await message.answer(text, reply_markup=get_subscription_keyboard(user.is_subscribed if user else True))
@router.callback_query(F.data.in_(["subscribe", "unsubscribe"])) if user:
async def toggle_subscription(callback: CallbackQuery): # Приветствие при повторном запуске
subscribe = callback.data == "subscribe" text = f"👋 Привет, {user.full_name or 'друг'}!\n" \
f"Группа: {user.study_group or 'не указана'}\n" \
"Используй кнопки ниже, чтобы управлять подпиской:"
await message.answer(text, reply_markup=get_subscription_keyboard(user.is_subscribed))
else:
# Первый запуск — регистрация
await state.set_state(Register.waiting_for_fullname)
await message.answer("👋 Привет! Пожалуйста, напиши свои ФИО одной строкой:")
@router.message(Register.waiting_for_fullname)
async def get_fullname(message: Message, state: FSMContext):
await state.update_data(full_name=message.text)
await state.set_state(Register.waiting_for_study_group)
await message.answer("Теперь укажи номер своей группы (например, Б11-111):")
@router.message(Register.waiting_for_study_group)
async def get_study_group(message: Message, state: FSMContext):
data = await state.get_data()
full_name = data["full_name"]
study_group = message.text
async with async_session() as session: async with async_session() as session:
await session.execute( await session.execute(insert(User).values(
update(User) user_id=message.from_user.id,
.where(User.user_id == callback.from_user.id) full_name=full_name,
.values(is_subscribed=subscribe) study_group=study_group,
) is_subscribed=True
))
await session.commit() await session.commit()
await callback.message.edit_reply_markup(reply_markup=get_subscription_keyboard(subscribe))
await callback.answer("Вы подписались!" if subscribe else "Вы отписались.") await message.answer(f"✅ Регистрация завершена!\nФИО: {full_name}\nГруппа: {study_group}")
await message.answer("Теперь ты можешь управлять подпиской ниже 👇", reply_markup=get_subscription_keyboard(True))
await state.clear()
+13 -3
View File
@@ -1,23 +1,33 @@
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
from aiogram.fsm.storage.memory import MemoryStorage from aiogram.fsm.storage.memory import MemoryStorage
from handlers import user, admin
from database import engine, Base
import asyncio import asyncio
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
from handlers import user, admin, admin_adv, adv_callbacks
from database import engine, Base
load_dotenv() load_dotenv()
async def on_startup(): async def on_startup():
async with engine.begin() as conn: async with engine.begin() as conn:
# await conn.run_sync(Base.metadata.drop_all) # Сброс БД
await conn.run_sync(Base.metadata.create_all) await conn.run_sync(Base.metadata.create_all)
async def main(): async def main():
bot = Bot(token=os.getenv("BOT_TOKEN")) bot = Bot(token=os.getenv("BOT_TOKEN"))
dp = Dispatcher(storage=MemoryStorage()) dp = Dispatcher(storage=MemoryStorage())
dp.include_routers(user.router, admin.router) dp.include_routers(
user.router,
admin.router,
admin_adv.router,
adv_callbacks.router
)
await on_startup() await on_startup()
await dp.start_polling(bot) await dp.start_polling(bot)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())
+3 -1
View File
@@ -1,8 +1,10 @@
from sqlalchemy import Column, BigInteger, Boolean from sqlalchemy import Column, BigInteger, Boolean, String
from database import Base from database import Base
class User(Base): class User(Base):
__tablename__ = "users" __tablename__ = "users"
user_id = Column(BigInteger, primary_key=True) user_id = Column(BigInteger, primary_key=True)
full_name = Column(String, nullable=False)
study_group = Column(String, nullable=False)
is_subscribed = Column(Boolean, default=True) is_subscribed = Column(Boolean, default=True)