Добавление логгирования

This commit is contained in:
ada-dmitry
2025-05-03 09:42:50 +03:00
parent e1063b669f
commit ec2c0c7dfc
21 changed files with 244 additions and 99 deletions
+4
View File
@@ -0,0 +1,4 @@
BOT_TOKEN=
ADMINS=
DATABASE_URL=
-8
View File
@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
-19
View File
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="postgres@localhost" uuid="64a7c47f-1c6d-4f9b-bb6d-5b6fe66fba41">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/postgres</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="vc_database" uuid="bdaa7723-af4b-4080-b3fe-20d0cf595d3c">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/postgres</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>
-6
View File
@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
-7
View File
@@ -1,7 +0,0 @@
<?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.13 (vc_bot)" project-jdk-type="Python SDK" />
</project>
-8
View File
@@ -1,8 +0,0 @@
<?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
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
-14
View File
@@ -1,14 +0,0 @@
<?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$/.idea/dataSources" />
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 (vc_bot)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PackageRequirementsSettings">
<option name="keepMatchingSpecifier" value="false" />
</component>
</module>
+92 -2
View File
@@ -1,3 +1,93 @@
## Бот для Волонтерского Центра НИЯУ МИФИ
# Volunteer Center Bot (Волонтерский Центр НИЯУ МИФИ Бот)
-
## Описание
Этот проект представляет собой Telegram-бота, разработанного для Волонтерского Центра НИЯУ МИФИ. Бот предоставляет функционал для регистрации пользователей, управления рассылками и взаимодействия с администраторами.
## Основной функционал:
1. **Регистрация пользователей:**
* Позволяет новым пользователям зарегистрироваться в системе бота.
* Данные пользователей сохраняются в базе данных.
2. **Подписка/Отписка от рассылки:**
* Пользователи могут подписываться на рассылку сообщений от администраторов.
* Пользователи могут отписываться от рассылки в любое время.
3. **Рассылка сообщений:**
* Администраторы могут отправлять сообщения пользователям, подписанным на рассылку.
* Поддерживаются два формата сообщений: короткий и длинный.
4. **Статистика рассылки:**
* Предоставляет администраторам информацию об объеме рассылки (например, количество получателей).
## Технологии:
* **Python:** Основной язык программирования.
* **aiogram:** Библиотека для создания Telegram-ботов.
* **SQLAlchemy:** ORM для работы с базой данных.
* **PostgreSQL:** Используется в качестве базы данных.
* **dotenv:** Для управления переменными окружения.
* **FSM (Finite State Machine):** Для управления состояниями бота.
* **MemoryStorage:** Хранилище состояний в оперативной памяти.
## Структура проекта:
* **`main.py`:** Основной файл, запускающий бота.
* **`handlers/`:** Директория, содержащая обработчики событий (хендлеры) для различных типов пользователей (обычные пользователи, администраторы) и callback-запросов.
* `user.py`: Обработчики для обычных пользователей.
* `admin.py`: Обработчики для администраторов.
* `admin_adv.py`: Обработчики для рассылки сообщений администраторами.
* `adv_callbacks.py`: Обработчики для callback-запросов, связанных с рассылкой.
* **`database/`:** Директория, содержащая код для работы с базой данных.
* `__init__.py`: Инициализация модуля.
* **`.env`:** Файл с переменными окружения (например, токен бота).
* **`README.md`:** Этот файл.
## Установка и запуск:
1. **Клонируйте репозиторий:**
```bash
git clone https://github.com/ada-dmitry/vol_bot
cd vol_bot
```
2. **Создайте виртуальное окружение (рекомендуется):**
```bash
python3 -m venv venv
source venv/bin/activate # Linux/macOS
venv\Scripts\activate # Windows
```
3. **Установите зависимости:**
```bash
pip install -r requirements.txt
```
(Если у вас нет файла `requirements.txt`, то установите зависимости вручную: `pip install aiogram python-dotenv sqlalchemy`)
4. **Создайте файл `.env`:**
* Скопируйте содержимое файла `.env.example` в новый файл `.env`.
* Замените `BOT_TOKEN` на токен вашего бота, полученный от BotFather.
5. **Запустите бота:**
```bash
python main.py
```
## Разработка:
* Для добавления нового функционала создавайте новые обработчики в соответствующих файлах директории `handlers/`.
* Для работы с базой данных используйте SQLAlchemy.
* Для управления состояниями бота используйте FSM.
## Планы по развитию:
* Добавление новых команд и функций.
* Улучшение интерфейса взаимодействия с пользователем.
* Расширение возможностей администраторов.
* Добавление логирования.
* Добавление тестов.
## Контакты
Другие проекты: [GitHub](https://github.com/ada-dmitry)
По вопросам и предложениям обращайтесь: [TG - Дмитрий](https://t.me/ada_dmitry)
Binary file not shown.
Binary file not shown.
+51
View File
@@ -0,0 +1,51 @@
2025-05-02 20:48:11,017 - INFO - __main__ - Запуск работы бота.
2025-05-02 20:48:11,070 - INFO - aiogram.dispatcher - Start polling
2025-05-02 20:48:11,331 - INFO - aiogram.dispatcher - Run polling for bot @volunteer_mephi_bot id=7827792861 - 'VC_Bot'
2025-05-02 20:48:38,874 - WARNING - aiogram.dispatcher - Received SIGINT signal
2025-05-02 20:48:38,875 - INFO - aiogram.dispatcher - Polling stopped for bot @volunteer_mephi_bot id=7827792861 - 'VC_Bot'
2025-05-02 20:48:38,875 - INFO - aiogram.dispatcher - Polling stopped
2025-05-02 20:52:05,968 - INFO - __main__ - Запуск работы бота.
2025-05-02 20:52:06,013 - INFO - aiogram.dispatcher - Start polling
2025-05-02 20:52:06,277 - INFO - aiogram.dispatcher - Run polling for bot @volunteer_mephi_bot id=7827792861 - 'VC_Bot'
2025-05-02 20:52:13,305 - WARNING - aiogram.dispatcher - Received SIGINT signal
2025-05-02 20:52:13,306 - INFO - aiogram.dispatcher - Polling stopped for bot @volunteer_mephi_bot id=7827792861 - 'VC_Bot'
2025-05-02 20:52:13,306 - INFO - aiogram.dispatcher - Polling stopped
2025-05-02 20:52:17,747 - INFO - __main__ - Запуск работы бота.
2025-05-02 20:52:17,780 - INFO - aiogram.dispatcher - Start polling
2025-05-02 20:52:18,048 - INFO - aiogram.dispatcher - Run polling for bot @volunteer_mephi_bot id=7827792861 - 'VC_Bot'
2025-05-02 20:52:32,713 - INFO - aiogram.event - Update id=328315775 is handled. Duration 226 ms by bot id=7827792861
2025-05-02 20:52:44,275 - INFO - aiogram.event - Update id=328315776 is not handled. Duration 1 ms by bot id=7827792861
2025-05-02 20:53:04,829 - INFO - aiogram.event - Update id=328315777 is not handled. Duration 2 ms by bot id=7827792861
2025-05-02 20:53:17,894 - WARNING - aiogram.dispatcher - Received SIGINT signal
2025-05-02 20:53:17,895 - INFO - aiogram.dispatcher - Polling stopped for bot @volunteer_mephi_bot id=7827792861 - 'VC_Bot'
2025-05-02 20:53:17,896 - INFO - aiogram.dispatcher - Polling stopped
2025-05-02 20:55:10,923 - INFO - __main__ - Запуск работы бота.
2025-05-02 20:55:10,971 - INFO - aiogram.dispatcher - Start polling
2025-05-02 20:55:11,246 - INFO - aiogram.dispatcher - Run polling for bot @volunteer_mephi_bot id=7827792861 - 'VC_Bot'
2025-05-02 20:55:19,000 - INFO - aiogram.event - Update id=328315778 is not handled. Duration 1 ms by bot id=7827792861
2025-05-02 20:55:20,367 - INFO - main - User 451218808 started the bot
2025-05-02 20:55:20,838 - INFO - aiogram.event - Update id=328315779 is handled. Duration 472 ms by bot id=7827792861
2025-05-02 21:00:28,136 - INFO - aiogram.event - Update id=328315780 is not handled. Duration 1 ms by bot id=7827792861
2025-05-02 21:00:28,840 - INFO - main - User 451218808 started the bot
2025-05-02 21:00:28,963 - INFO - aiogram.event - Update id=328315781 is handled. Duration 123 ms by bot id=7827792861
2025-05-02 21:00:31,620 - INFO - aiogram.event - Update id=328315782 is not handled. Duration 2 ms by bot id=7827792861
2025-05-02 21:00:33,258 - INFO - aiogram.event - Update id=328315783 is not handled. Duration 1 ms by bot id=7827792861
2025-05-02 21:00:33,973 - INFO - aiogram.event - Update id=328315784 is not handled. Duration 1 ms by bot id=7827792861
2025-05-02 21:00:34,039 - INFO - aiogram.event - Update id=328315785 is not handled. Duration 1 ms by bot id=7827792861
2025-05-02 21:00:34,178 - INFO - aiogram.event - Update id=328315786 is not handled. Duration 2 ms by bot id=7827792861
2025-05-02 21:00:34,389 - INFO - aiogram.event - Update id=328315787 is not handled. Duration 1 ms by bot id=7827792861
2025-05-02 21:08:22,430 - WARNING - aiogram.dispatcher - Received SIGINT signal
2025-05-02 21:08:22,431 - INFO - aiogram.dispatcher - Polling stopped for bot @volunteer_mephi_bot id=7827792861 - 'VC_Bot'
2025-05-02 21:08:22,431 - INFO - aiogram.dispatcher - Polling stopped
2025-05-02 21:16:06,283 - INFO - __main__ - Запуск работы бота.
2025-05-02 21:16:06,343 - INFO - aiogram.dispatcher - Start polling
2025-05-02 21:16:06,584 - INFO - aiogram.dispatcher - Run polling for bot @volunteer_mephi_bot id=7827792861 - 'VC_Bot'
2025-05-02 21:16:23,053 - INFO - handlers.user - User 451218808 started the bot
2025-05-02 21:16:23,296 - INFO - aiogram.event - Update id=328315793 is handled. Duration 243 ms by bot id=7827792861
2025-05-02 21:16:24,462 - INFO - aiogram.event - Update id=328315794 is handled. Duration 220 ms by bot id=7827792861
2025-05-02 21:16:25,683 - INFO - aiogram.event - Update id=328315795 is handled. Duration 234 ms by bot id=7827792861
2025-05-02 21:16:27,117 - INFO - aiogram.event - Update id=328315796 is handled. Duration 212 ms by bot id=7827792861
2025-05-02 21:16:28,052 - INFO - aiogram.event - Update id=328315797 is handled. Duration 381 ms by bot id=7827792861
2025-05-02 21:16:30,962 - INFO - aiogram.event - Update id=328315798 is handled. Duration 200 ms by bot id=7827792861
2025-05-03 09:36:21,305 - INFO - __main__ - Запуск работы бота.
2025-05-03 09:37:58,037 - INFO - __main__ - Запуск работы бота.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+7 -1
View File
@@ -5,24 +5,30 @@ 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())
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:
+1 -5
View File
@@ -11,11 +11,10 @@ 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()
@@ -44,18 +43,15 @@ async def get_full_text_and_send(message: Message, state: FSMContext, bot: Bot):
# Генерируем уникальный 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}"),
+64 -14
View File
@@ -3,35 +3,47 @@ from aiogram.fsm.context import FSMContext
from aiogram.types import Message, CallbackQuery
from aiogram.fsm.state import State, StatesGroup
from sqlalchemy import select, insert, update
import logging
from models import User
from database import async_session
from keyboards import get_subscription_keyboard
logger = logging.getLogger(__name__)
router = Router()
# Машина состояний
class Register(StatesGroup):
waiting_for_fullname = State()
waiting_for_study_group = State()
@router.message(F.text == "/start")
async def start_handler(message: Message, state: FSMContext):
user_id = message.from_user.id
logger.info(f"User {message.from_user.id} started the bot")
async with async_session() as session:
result = await session.execute(select(User).where(User.user_id == user_id))
user = result.scalar_one_or_none()
if user:
# Приветствие при повторном запуске
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))
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("👋 Привет! Пожалуйста, напиши свои ФИО одной строкой:")
await message.answer(
"👋 Привет! Пожалуйста, напиши свои ФИО одной строкой:"
)
@router.message(Register.waiting_for_fullname)
async def get_fullname(message: Message, state: FSMContext):
@@ -39,6 +51,7 @@ async def get_fullname(message: Message, state: FSMContext):
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()
@@ -46,14 +59,51 @@ async def get_study_group(message: Message, state: FSMContext):
study_group = message.text
async with async_session() as session:
await session.execute(insert(User).values(
user_id=message.from_user.id,
full_name=full_name,
study_group=study_group,
is_subscribed=True
))
await session.execute(
insert(User).values(
user_id=message.from_user.id,
full_name=full_name,
study_group=study_group,
is_subscribed=True,
)
)
await session.commit()
await message.answer(f"✅ Регистрация завершена!\nФИО: {full_name}\nГруппа: {study_group}")
await message.answer("Теперь ты можешь управлять подпиской ниже 👇", reply_markup=get_subscription_keyboard(True))
await message.answer(
f"✅ Регистрация завершена!\nФИО: {full_name}\nГруппа: {study_group}"
)
await message.answer(
"Теперь ты можешь управлять подпиской ниже 👇",
reply_markup=get_subscription_keyboard(True),
)
await state.clear()
@router.callback_query(F.data == "subscribe")
async def subscribe(callback: CallbackQuery):
async with async_session() as session:
await session.execute(
update(User)
.where(User.user_id == callback.from_user.id)
.values(is_subscribed=True)
)
await session.commit()
await callback.message.edit_reply_markup(
reply_markup=get_subscription_keyboard(True)
)
await callback.answer("✅ Вы подписались на рассылку!")
@router.callback_query(F.data == "unsubscribe")
async def unsubscribe(callback: CallbackQuery):
async with async_session() as session:
await session.execute(
update(User)
.where(User.user_id == callback.from_user.id)
.values(is_subscribed=False)
)
await session.commit()
await callback.message.edit_reply_markup(
reply_markup=get_subscription_keyboard(False)
)
await callback.answer("❌ Вы отписались от рассылки.")
+24 -7
View File
@@ -4,30 +4,47 @@ from aiogram.fsm.storage.memory import MemoryStorage
import asyncio
import os
from dotenv import load_dotenv
import logging
from handlers import user, admin, admin_adv, adv_callbacks
from database import engine, Base
load_dotenv()
logging.basicConfig(
level=logging.INFO, # Уровень логирования (DEBUG, INFO, WARNING, ERROR, CRITICAL)
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
handlers=[
logging.FileHandler("bot.log"), # Логирование в файл bot.log
# logging.StreamHandler(), # Логирование в консоль
],
)
logger = logging.getLogger(__name__)
async def on_startup():
"""
Основная процедура для запуска работы с БД чреез SQLAlchemy.
"""
async with engine.begin() as conn:
# await conn.run_sync(Base.metadata.drop_all) # Сброс БД
await conn.run_sync(Base.metadata.create_all)
async def main():
bot = Bot(token=os.getenv("BOT_TOKEN"))
"""
Функция запуска бота, подключение к TG_API, создание диспетчера, подключение роутеров и запуск бота в режиме пуллинг.
"""
bot = Bot(token=os.getenv("BOT_TOKEN")) # type: ignore
dp = Dispatcher(storage=MemoryStorage())
logger.info("Запуск работы бота.")
dp.include_routers(
user.router,
admin.router,
admin_adv.router,
adv_callbacks.router
user.router, admin.router, admin_adv.router, adv_callbacks.router
)
await on_startup()
await dp.start_polling(bot)
await dp.start_polling(bot) # Бот в пуллинг режиме - "почтальон для API"
if __name__ == "__main__":
asyncio.run(main())
-1
View File
@@ -1,5 +1,4 @@
aiogram~=3.20.0
dotenv~=0.9.9
python-dotenv~=1.1.0
sqlalchemy~=2.0.40
asyncpg~=0.30.0