init project

This commit is contained in:
ada-dmitry
2025-04-15 15:35:05 +03:00
commit c0fe2af364
15 changed files with 191 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
.env
.venv
+8
View File
@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+6
View File
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (vol_bot)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (vol_bot)" project-jdk-type="Python SDK" />
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/vol_bot.iml" filepath="$PROJECT_DIR$/.idea/vol_bot.iml" />
</modules>
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (vol_bot)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+3
View File
@@ -0,0 +1,3 @@
## Бот для Волонтерского Центра НИЯУ МИФИ
-
+11
View File
@@ -0,0 +1,11 @@
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from sqlalchemy.orm import declarative_base
import os
from dotenv import load_dotenv
load_dotenv()
Base = declarative_base()
engine = create_async_engine(os.getenv("DATABASE_URL"))
async_session = async_sessionmaker(engine, expire_on_commit=False)
+51
View File
@@ -0,0 +1,51 @@
from aiogram import Router, F, Bot
from aiogram.types import Message
from sqlalchemy import select
from models import User
from database import async_session
import os
router = Router()
admin_ids = list(map(int, os.getenv("ADMINS").split(",")))
@router.message(F.text == "/admin")
async def admin_panel(message: Message):
if message.from_user.id not in admin_ids:
return await message.answer("Доступ запрещён.")
await message.answer("Команды:\n/stat — статистика\n/broadcast <текст> — рассылка")
@router.message(F.text.startswith("/stat"))
async def stat_handler(message: Message):
if message.from_user.id not in admin_ids:
return
async with async_session() as session:
total = await session.scalar(select(User).count())
subscribed = await session.scalar(select(User).where(User.is_subscribed).count())
await message.answer(f"Всего пользователей: {total}\nПодписаны: {subscribed}")
@router.message(F.text.startswith("/broadcast "))
async def broadcast_handler(message: Message, bot: Bot):
if message.from_user.id not in admin_ids:
await message.answer("Доступ запрещён.")
return
text = message.text.removeprefix("/broadcast ").strip()
if not text:
await message.answer("Текст рассылки не найден.")
return
sent = 0
failed = 0
async with async_session() as session:
result = await session.execute(select(User.user_id).where(User.is_subscribed))
users = result.scalars().all()
for uid in users:
try:
await bot.send_message(uid, text)
sent += 1
except Exception:
failed += 1
await message.answer(f"📬 Рассылка завершена:\nУспешно: {sent}\nОшибок: {failed}")
+33
View File
@@ -0,0 +1,33 @@
from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from sqlalchemy import select, insert, update
from models import User
from database import async_session
from keyboards import get_subscription_keyboard
router = Router()
@router.message(F.text == "/start")
async def start_handler(message: Message):
async with async_session() as session:
result = await session.execute(select(User).where(User.user_id == message.from_user.id))
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"]))
async def toggle_subscription(callback: CallbackQuery):
subscribe = callback.data == "subscribe"
async with async_session() as session:
await session.execute(
update(User)
.where(User.user_id == callback.from_user.id)
.values(is_subscribed=subscribe)
)
await session.commit()
await callback.message.edit_reply_markup(reply_markup=get_subscription_keyboard(subscribe))
await callback.answer("Вы подписались!" if subscribe else "Вы отписались.")
+11
View File
@@ -0,0 +1,11 @@
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
def get_subscription_keyboard(subscribed: bool):
if subscribed:
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="❌ Отписаться", callback_data="unsubscribe")]
])
else:
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="✅ Подписаться", callback_data="subscribe")]
])
+23
View File
@@ -0,0 +1,23 @@
from aiogram import Bot, Dispatcher
from aiogram.fsm.storage.memory import MemoryStorage
from handlers import user, admin
from database import engine, Base
import asyncio
import os
from dotenv import load_dotenv
load_dotenv()
async def on_startup():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async def main():
bot = Bot(token=os.getenv("BOT_TOKEN"))
dp = Dispatcher(storage=MemoryStorage())
dp.include_routers(user.router, admin.router)
await on_startup()
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())
+8
View File
@@ -0,0 +1,8 @@
from sqlalchemy import Column, BigInteger, Boolean
from database import Base
class User(Base):
__tablename__ = "users"
user_id = Column(BigInteger, primary_key=True)
is_subscribed = Column(Boolean, default=True)
+4
View File
@@ -0,0 +1,4 @@
aiogram~=3.19.0
SQLAlchemy~=2.0.40
# dotenv~=0.9.9
python-dotenv~=1.1.0