start
This commit is contained in:
286
bot.py
Normal file
286
bot.py
Normal file
@@ -0,0 +1,286 @@
|
||||
|
||||
import os
|
||||
from io import BytesIO
|
||||
from aiogram.types import KeyboardButton, ReplyKeyboardMarkup
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.fsm.storage.memory import MemoryStorage
|
||||
from aiogram.types import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
||||
from aiogram.filters.state import StateFilter
|
||||
TOKEN = "8433819654:AAGbK2UVrGiP-S5cfLn_B6pki2PR6cV2syQ"
|
||||
|
||||
# ===== PostgreSQL =====
|
||||
conn = psycopg2.connect(
|
||||
dbname="botdb",
|
||||
user="botuser",
|
||||
password="tpg44dm*oOas&2geWc^Paw7",
|
||||
host="127.0.0.1"
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
bot = Bot(token=TOKEN)
|
||||
dp = Dispatcher(storage=MemoryStorage())
|
||||
|
||||
# ===== FSM =====
|
||||
class Form(StatesGroup):
|
||||
consent = State()
|
||||
fio = State()
|
||||
dob = State()
|
||||
phone = State()
|
||||
city = State()
|
||||
klimov = State()
|
||||
|
||||
# ===== /start =====
|
||||
@dp.message(Command("start"))
|
||||
async def start(message: types.Message, state: FSMContext):
|
||||
kb = [
|
||||
[KeyboardButton(text="✅ Да, согласен"), KeyboardButton(text="❌ Нет")]
|
||||
]
|
||||
keyboard = ReplyKeyboardMarkup(keyboard=kb, resize_keyboard=True)
|
||||
await message.answer(
|
||||
"Привет! Я помогу пройти тест Климова.\n\n"
|
||||
"Сначала — согласие на обработку персональных данных.\n"
|
||||
"Ты согласен?",
|
||||
reply_markup=keyboard
|
||||
)
|
||||
await state.set_state(Form.consent)
|
||||
|
||||
# ===== Согласие =====
|
||||
@dp.message(Form.consent)
|
||||
async def consent(message: types.Message, state: FSMContext):
|
||||
if message.text.startswith("✅"):
|
||||
await state.set_state(Form.fio)
|
||||
await message.answer("Введите своё ФИО:", reply_markup=ReplyKeyboardRemove())
|
||||
else:
|
||||
await message.answer("Без согласия мы не можем продолжить 🙏", reply_markup=ReplyKeyboardRemove())
|
||||
await state.clear()
|
||||
|
||||
# ===== ФИО =====
|
||||
@dp.message(Form.fio)
|
||||
async def fio(message: types.Message, state: FSMContext):
|
||||
await state.update_data(fio=message.text)
|
||||
await state.set_state(Form.dob)
|
||||
await message.answer("Введите дату рождения (ДД.MM.ГГГГ):")
|
||||
|
||||
# ===== Дата рождения =====
|
||||
@dp.message(Form.dob)
|
||||
async def dob(message: types.Message, state: FSMContext):
|
||||
await state.update_data(dob=message.text)
|
||||
await state.set_state(Form.phone)
|
||||
await message.answer("Введите номер телефона:")
|
||||
|
||||
# ===== Телефон =====
|
||||
@dp.message(Form.phone)
|
||||
async def phone(message: types.Message, state: FSMContext):
|
||||
await state.update_data(phone=message.text)
|
||||
await state.set_state(Form.city)
|
||||
await message.answer("Из какого ты населённого пункта?")
|
||||
|
||||
# ===== Город + меню тестов =====
|
||||
@dp.message(Form.city)
|
||||
async def city(message: types.Message, state: FSMContext):
|
||||
await state.update_data(city=message.text)
|
||||
data = await state.get_data()
|
||||
|
||||
# Преобразуем дату в формат PostgreSQL
|
||||
try:
|
||||
dob_parsed = datetime.strptime(data["dob"], "%d.%m.%Y").date()
|
||||
except ValueError:
|
||||
await message.answer("Неверный формат даты. Введите дату в формате ДД.MM.ГГГГ")
|
||||
await state.set_state(Form.dob)
|
||||
return
|
||||
|
||||
try:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO users (telegram_id, fio, dob, phone, city)
|
||||
VALUES (%s, %s, %s, %s, %s)
|
||||
ON CONFLICT (telegram_id) DO UPDATE
|
||||
SET fio=EXCLUDED.fio, dob=EXCLUDED.dob, phone=EXCLUDED.phone, city=EXCLUDED.city
|
||||
""",
|
||||
(message.from_user.id, data["fio"], dob_parsed, data["phone"], data["city"])
|
||||
)
|
||||
conn.commit()
|
||||
await message.answer("Анкета сохранена ✅")
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
await message.answer(f"Ошибка при сохранении: {e}")
|
||||
return
|
||||
|
||||
kb = [
|
||||
[KeyboardButton(text="📘 Тест Климова")],
|
||||
[KeyboardButton(text="📗 Тест №2"), KeyboardButton(text="📙 Тест №3")]
|
||||
]
|
||||
keyboard = ReplyKeyboardMarkup(keyboard=kb, resize_keyboard=True)
|
||||
await state.clear()
|
||||
await message.answer("Теперь выбери тест:", reply_markup=keyboard)
|
||||
|
||||
# ===== Выбор теста после анкеты =====
|
||||
# Выбор теста — только когда FSM пустой (после анкеты)
|
||||
@dp.message(StateFilter(None))
|
||||
async def select_test(message: types.Message, state: FSMContext):
|
||||
if message.text == "📘 Тест Климова":
|
||||
cur.execute("SELECT id, text1, category1, text2, category2, image FROM klimov_questions ORDER BY id")
|
||||
questions = cur.fetchall()
|
||||
await state.update_data(klimov_questions=questions, klimov_index=0, klimov_result={})
|
||||
await state.set_state(Form.klimov)
|
||||
await send_klimov_question(message.from_user.id, state)
|
||||
else:
|
||||
await message.answer("Пока этот тест не реализован.")
|
||||
|
||||
import os
|
||||
from aiogram.types import KeyboardButton, ReplyKeyboardMarkup, FSInputFile
|
||||
from aiogram.fsm.context import FSMContext
|
||||
|
||||
async def send_klimov_question(user_id, state: FSMContext):
|
||||
data = await state.get_data()
|
||||
index = data["klimov_index"]
|
||||
questions = data["klimov_questions"]
|
||||
|
||||
if index >= len(questions):
|
||||
await send_klimov_result(user_id, state)
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
q = questions[index] # (id, text1, category1, text2, category2, image)
|
||||
|
||||
# Клавиатура с вариантами
|
||||
kb = [
|
||||
[KeyboardButton(text=q[1])],
|
||||
[KeyboardButton(text=q[3])]
|
||||
]
|
||||
keyboard = ReplyKeyboardMarkup(keyboard=kb, resize_keyboard=True)
|
||||
|
||||
# Отправка картинки, если есть
|
||||
image_filename = os.path.basename(q[5].strip()) if q[5] else None
|
||||
if image_filename:
|
||||
image_path = os.path.join(os.getcwd(), "images", image_filename)
|
||||
print("DEBUG: image_path =", image_path, "exists =", os.path.isfile(image_path))
|
||||
if os.path.isfile(image_path):
|
||||
photo = FSInputFile(image_path) # вот ключевой момент
|
||||
await bot.send_photo(
|
||||
chat_id=user_id,
|
||||
photo=photo,
|
||||
caption=f"Вопрос {index+1} из {len(questions)}:\nВыберите вариант",
|
||||
reply_markup=keyboard
|
||||
)
|
||||
return
|
||||
|
||||
# Если картинки нет, просто отправляем текст
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=f"Вопрос {index+1} из {len(questions)}:\nВыберите вариант",
|
||||
reply_markup=keyboard
|
||||
)
|
||||
|
||||
# ===== Ответ на вопросы =====
|
||||
@dp.message(StateFilter(Form.klimov))
|
||||
async def klimov_answer(message: types.Message, state: FSMContext):
|
||||
data = await state.get_data()
|
||||
index = data["klimov_index"]
|
||||
questions = data["klimov_questions"]
|
||||
result = data["klimov_result"]
|
||||
|
||||
q = questions[index]
|
||||
|
||||
# Проверяем какой вариант выбрал пользователь
|
||||
if message.text == q[1]:
|
||||
result[q[2]] = result.get(q[2], 0) + 1
|
||||
elif message.text == q[3]:
|
||||
result[q[4]] = result.get(q[4], 0) + 1
|
||||
|
||||
index += 1
|
||||
await state.update_data(klimov_index=index, klimov_result=result)
|
||||
|
||||
# Переходим к следующему вопросу
|
||||
await send_klimov_question(message.from_user.id, state)
|
||||
|
||||
|
||||
# ===== Функция показа вопроса =====
|
||||
# Функция показа вопроса
|
||||
|
||||
|
||||
# ===== Функция для сохранения результата в БД =====
|
||||
async def save_klimov_result(user_id, state: FSMContext):
|
||||
data = await state.get_data()
|
||||
result = data.get("klimov_result", {})
|
||||
|
||||
conn = psycopg2.connect(dbname="botdb", user="botuser", password="tpg44dm*oOas&2geWc^Paw7")
|
||||
cur = conn.cursor()
|
||||
|
||||
# Получаем user_id из таблицы users
|
||||
cur.execute("SELECT id FROM users WHERE telegram_id = %s", (user_id,))
|
||||
user_row = cur.fetchone()
|
||||
if user_row:
|
||||
db_user_id = user_row[0]
|
||||
cur.execute(
|
||||
"INSERT INTO results (user_id, test_name, result) VALUES (%s, %s, %s)",
|
||||
(db_user_id, "Тест Климова", psycopg2.extras.Json(result))
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
# Словарь с описаниями профессий по категориям
|
||||
professions_desc = {
|
||||
"Человек-природа": "К типу 'человек-природа' можно отнести профессии, связанные с изучением живой и неживой природы (микробиолог, агрохимик, геолог), с уходом за растениями и животными (лесовод, овощевод, зоотехник).",
|
||||
"Человек-техника": "Тип 'человек-техника' включает в себя профессии, связанные с созданием, монтажом, сборкой и наладкой технических устройств (каменщик, монтажник, сварщик, инженер-конструктор), эксплуатацией технических средств (водитель, крановщик, токарь, швея), ремонтом техники.",
|
||||
"Человек-человек": "К профессиям типа 'человек-человек' относятся профессии, связанные с медицинским обслуживанием (врач, медсестра, санитарка), обучением и воспитанием (воспитатель, няня, учитель, преподаватель, тренер), бытовым обслуживанием (продавец, проводник, официант).",
|
||||
"Человек-знаковая система": "Тип 'человек - знаковая система' объединяет профессии, связанные с текстами (корректор, оператор ПК, переводчик, библиотекарь), с цифрами, формулами и таблицами (программист, экономист, бухгалтер, кассир), с чертежами, картами, схемами (штурман, чертежник).",
|
||||
"Человек-художественный образ": "К типу 'человек - художественный образ' можно отнести профессии, связанные с созданием, проектированием, моделированием художественных произведений (художник, журналист, модельер, композитор), с воспроизведением, изготовлением различных изделий по эскизу, образцу."
|
||||
}
|
||||
# ===== Функция для форматированного вывода результатов =====
|
||||
# ===== Форматированный вывод результатов с процентами по максимуму =====
|
||||
async def send_klimov_result(user_id, state: FSMContext):
|
||||
data = await state.get_data()
|
||||
result = data.get("klimov_result", {})
|
||||
questions = data.get("klimov_questions", [])
|
||||
|
||||
if not questions:
|
||||
await bot.send_message(user_id, "Вы не ответили ни на один вопрос.")
|
||||
return
|
||||
|
||||
# Подсчёт максимального количества очков для каждой категории
|
||||
max_points = {}
|
||||
for q in questions:
|
||||
# q[2] и q[4] — категории для вариантов
|
||||
max_points[q[2]] = max_points.get(q[2], 0) + 1
|
||||
max_points[q[4]] = max_points.get(q[4], 0) + 1
|
||||
|
||||
output = "Ваши результаты:\n"
|
||||
for category, max_pt in max_points.items():
|
||||
pts = result.get(category, 0)
|
||||
percent = (pts / max_pt) * 100
|
||||
output += f"- {category} - {percent:.1f}%\n"
|
||||
|
||||
# Определяем категорию с наибольшим результатом
|
||||
if result:
|
||||
top_category = max(result, key=result.get)
|
||||
output += f"\nТип профессии: {top_category}"
|
||||
|
||||
await bot.send_message(user_id, output, reply_markup=types.ReplyKeyboardRemove())
|
||||
|
||||
# Затем по очереди отправляем описания
|
||||
for category in ["Человек-природа", "Человек-техника", "Человек-человек",
|
||||
"Человек-знаковая система", "Человек-художественный образ"]:
|
||||
await bot.send_message(user_id, f"🟢 {category}\n\n{professions_desc[category]}")
|
||||
|
||||
# Последнее сообщение с контактами / ссылками
|
||||
await bot.send_message(
|
||||
user_id,
|
||||
" Задать вопрос или записаться на индивидуальную консультацию по построению образовательного маршрута можно здесь: @chrtakova11\n\n"
|
||||
"Узнать подробнее о профессиях: https://postupi.online/professii/so/"
|
||||
)
|
||||
# ===== Запуск бота =====
|
||||
async def main():
|
||||
await dp.start_polling(bot)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user