feat: add subscribtion scheduler with ai, pagination

This commit is contained in:
Faynot
2026-03-29 11:25:31 +03:00
parent 1db524f757
commit 164acd6307
16 changed files with 688 additions and 358 deletions

159
bot.py
View File

@@ -1,156 +1,29 @@
import asyncio
import logging
import sys
from os import getenv
from aiogram import Bot, Dispatcher, html, F
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.filters import CommandStart, Command
from aiogram.types import Message, InlineKeyboardButton, CallbackQuery
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from dotenv import load_dotenv
from scheduler import VacancyScanner
from config import TOKEN
from database import Database
load_dotenv()
TOKEN = getenv("BOT_TOKEN")
dp = Dispatcher()
db = Database("users.db")
# Добавили состояние для ручного ввода сферы
class Registration(StatesGroup):
waiting_for_sphere = State()
waiting_for_custom_sphere = State() # <-- Новое состояние
waiting_for_language = State()
waiting_for_preferences = State()
@dp.message(CommandStart())
async def command_start_handler(message: Message) -> None:
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="✅ Подписаться", callback_data="subscribe"))
builder.row(InlineKeyboardButton(text="📄 Читать оферту", url="https://telegra.ph/Polzovatelskoe-soglashenie-i-Oferta-qwork-parse-bot-03-28"))
text = (
"<b>👋 Привет! Я твой персональный агент по Kwork.</b>\n\n"
"💻 ⚠️ <b>ВАЖНО:</b> Этот бот предназначен <b>исключительно для IT-специалистов</b>.\n\n"
"🔍 Я мониторю биржу 24/7 и мгновенно присылаю тебе свежие заказы.\n\n"
"Нажимая кнопку «Подписаться», вы принимаете условия "
"<a href='https://telegra.ph/Polzovatelskoe-soglashenie-i-Oferta-qwork-parse-bot-03-28'>публичной оферты</a>."
)
await message.answer(text, reply_markup=builder.as_markup(), disable_web_page_preview=True)
@dp.message(Command("profile"))
async def show_profile(message: Message):
user_data = await db.get_user(message.from_user.id)
if not user_data or user_data[0] is None:
await message.answer("⚠️ Твой профиль еще не настроен. Нажми /start, чтобы начать.")
return
sphere, lang, prefs = user_data
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="📝 Редактировать профиль", callback_data="subscribe")) # Используем тот же callback
text = (
"<b>👤 Твой профиль IT-специалиста:</b>\n\n"
f"<b>🌐 Сфера:</b> {sphere}\n"
f"<b>🛠 Стек:</b> {lang}\n"
f"<b>⚙️ Предпочтения:</b> {prefs}\n\n"
"Хочешь что-то изменить? Нажми кнопку ниже."
)
await message.answer(text, reply_markup=builder.as_markup())
@dp.callback_query(F.data == "subscribe")
async def subscribe_handler(callback: CallbackQuery, state: FSMContext):
await db.add_user(callback.from_user.id)
builder = InlineKeyboardBuilder()
spheres = ["Backend", "Frontend", "Mobile", "DevOps", "Design", "QA"]
for sphere in spheres:
builder.add(InlineKeyboardButton(text=sphere, callback_data=f"sphere_{sphere}"))
# Добавляем кнопку своего варианта
builder.row(InlineKeyboardButton(text="⌨️ Свой вариант", callback_data="sphere_other"))
builder.adjust(2)
await callback.message.edit_text(
"Отлично! Давай настроим профиль.\n<b>В какой сфере IT ты работаешь?</b>",
reply_markup=builder.as_markup()
)
await state.set_state(Registration.waiting_for_sphere)
await callback.answer()
@dp.callback_query(Registration.waiting_for_sphere)
async def sphere_chosen(callback: CallbackQuery, state: FSMContext):
sphere = callback.data.split("_")[1]
if sphere == "other":
await callback.message.edit_text("Напиши свою сферу деятельности (например: <i>Data Science</i> или <i>GameDev</i>):")
await state.set_state(Registration.waiting_for_custom_sphere)
else:
await state.update_data(sphere=sphere)
await callback.message.edit_text(
f"Выбрано: <b>{sphere}</b>\n\nКакой основной <b>язык программирования</b> или стек технологий используешь?"
)
await state.set_state(Registration.waiting_for_language)
await callback.answer()
# Обработчик для текстового ввода своей сферы
@dp.message(Registration.waiting_for_custom_sphere)
async def custom_sphere_input(message: Message, state: FSMContext):
sphere = message.text
await state.update_data(sphere=sphere)
await message.answer(
f"Принято: <b>{sphere}</b>\n\nКакой основной <b>язык программирования</b> или стек технологий используешь?"
)
await state.set_state(Registration.waiting_for_language)
@dp.message(Registration.waiting_for_language)
async def language_chosen(message: Message, state: FSMContext):
await state.update_data(language=message.text)
# Создаем кнопку для пропуска
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="⏩ Пропустить", callback_data="skip_preferences"))
await message.answer(
"Принято! И последнее: напиши свои <b>предпочтения по заказам</b> (фильтры).\n"
"Например: 'чек от 5000р' или 'без правок'.\n\n"
"<i>Если не хочешь заполнять сейчас, нажми кнопку ниже.</i>",
reply_markup=builder.as_markup()
)
await state.set_state(Registration.waiting_for_preferences)
@dp.callback_query(Registration.waiting_for_preferences, F.data == "skip_preferences")
async def skip_preferences(callback: CallbackQuery, state: FSMContext):
await state.update_data(preferences="Не указано") # Устанавливаем значение по умолчанию
user_data = await state.get_data()
await db.update_user_data(callback.from_user.id, user_data)
await callback.message.edit_text(
"✅ <b>Профиль успешно настроен!</b> (Фильтры пропущены)\n\n"
f"Сфера: {user_data['sphere']}\n"
f"Стек: {user_data['language']}\n"
f"Фильтры: {user_data['preferences']}"
)
await state.clear()
await callback.answer()
async def on_startup():
await db.create_tables()
print("Database ready")
from handlers import routers
async def main() -> None:
bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
dp.startup.register(on_startup)
dp = Dispatcher()
# Регистрация всех роутеров
dp.include_routers(*routers)
# Инициализация БД
db = Database("users.db")
await db.create_tables()
scanner = VacancyScanner(bot, db)
asyncio.create_task(scanner.start_scanning())
await dp.start_polling(bot)
if __name__ == "__main__":