commit 8ad2bfbe59a1cb920b12694c797b30ad2e4aa503 Author: jze9 Date: Sun Sep 14 17:30:02 2025 +0500 start diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..3a2826e --- /dev/null +++ b/bot.py @@ -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()) diff --git a/bot2.py b/bot2.py new file mode 100644 index 0000000..d8f34fc --- /dev/null +++ b/bot2.py @@ -0,0 +1,176 @@ +from aiogram import Bot, Dispatcher, types +from aiogram.filters import Command, StateFilter +from aiogram.fsm.state import State, StatesGroup +from aiogram.fsm.context import FSMContext +from aiogram.fsm.storage.memory import MemoryStorage +from aiogram.types import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove +import psycopg2 +import psycopg2.extras +from datetime import datetime +import os +from aiogram.types import FSInputFile + +TOKEN = "ВАШ_TOKEN" +bot = Bot(token=TOKEN) +dp = Dispatcher(storage=MemoryStorage()) + +# ===== FSM ===== +class Form(StatesGroup): + consent = State() + fio = State() + dob = State() + phone = State() + city = State() + klimov = State() +@dp.message(Command("start")) +async def start(message: types.Message, state: FSMContext): + kb = [[KeyboardButton("✅ Да, согласен"), KeyboardButton("❌ Нет")]] + keyboard = ReplyKeyboardMarkup(keyboard=kb, resize_keyboard=True) + await message.answer( + "Привет! Сначала нужно согласие на обработку персональных данных. Согласны?", + 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() + + # Сохраняем в БД + try: + dob_parsed = datetime.strptime(data["dob"], "%d.%m.%Y").date() + except ValueError: + await message.answer("Неверный формат даты. Введите ДД.MM.ГГГГ") + await state.set_state(Form.dob) + return + + conn = psycopg2.connect(dbname="botdb", user="botuser", password="tpg44dm*oOas&2geWc^Paw7") + cur = conn.cursor() + 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() + cur.close() + conn.close() + + kb = [[KeyboardButton("📘 Тест Климова")]] + keyboard = ReplyKeyboardMarkup(keyboard=kb, resize_keyboard=True) + await state.clear() + await message.answer("Анкета сохранена ✅ Выберите тест:", reply_markup=keyboard) +@dp.message(StateFilter(None)) +async def select_test(message: types.Message, state: FSMContext): + if message.text == "📘 Тест Климова": + conn = psycopg2.connect(dbname="botdb", user="botuser", password="ваш_пароль") + cur = conn.cursor() + cur.execute("SELECT id, text1, category1, text2, category2, image FROM klimov_questions ORDER BY id") + questions = cur.fetchall() + cur.close() + conn.close() + + 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("Пока этот тест не реализован.") +async def send_klimov_question(user_id, state: FSMContext): + data = await state.get_data() + index = data.get("klimov_index", 0) + questions = data.get("klimov_questions", []) + + if index >= len(questions): + await send_klimov_result(user_id, state) + await state.clear() + return + + q = questions[index] + kb = [[KeyboardButton(q[1])], [KeyboardButton(q[3])]] + keyboard = ReplyKeyboardMarkup(keyboard=kb, resize_keyboard=True) + + image_path = q[5] + if image_path and os.path.exists(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(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 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: + 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 cat, max_pt in max_points.items(): + pts = result.get(cat, 0) + percent = (pts / max_pt) * 100 + output += f"- {cat}: {percent:.1f}%\n" + + if result: + top_cat = max(result, key=result.get) + output += f"\nТип профессии: {top_cat}" + + await bot.send_message(user_id, output, reply_markup=ReplyKeyboardRemove()) diff --git a/images/10.jpg b/images/10.jpg new file mode 100644 index 0000000..eed0476 Binary files /dev/null and b/images/10.jpg differ diff --git a/images/11.jpg b/images/11.jpg new file mode 100644 index 0000000..6cb38e8 Binary files /dev/null and b/images/11.jpg differ diff --git a/images/12.jpg b/images/12.jpg new file mode 100644 index 0000000..002c7b7 Binary files /dev/null and b/images/12.jpg differ diff --git a/images/13.jpg b/images/13.jpg new file mode 100644 index 0000000..84e24ef Binary files /dev/null and b/images/13.jpg differ diff --git a/images/14.jpg b/images/14.jpg new file mode 100644 index 0000000..2444981 Binary files /dev/null and b/images/14.jpg differ diff --git a/images/15.jpg b/images/15.jpg new file mode 100644 index 0000000..0866f42 Binary files /dev/null and b/images/15.jpg differ diff --git a/images/16.jpg b/images/16.jpg new file mode 100644 index 0000000..806a3ad Binary files /dev/null and b/images/16.jpg differ diff --git a/images/17.jpg b/images/17.jpg new file mode 100644 index 0000000..986acaa Binary files /dev/null and b/images/17.jpg differ diff --git a/images/18.jpg b/images/18.jpg new file mode 100644 index 0000000..cd055ee Binary files /dev/null and b/images/18.jpg differ diff --git a/images/19.jpg b/images/19.jpg new file mode 100644 index 0000000..f4c3d2e Binary files /dev/null and b/images/19.jpg differ diff --git a/images/2.jpg b/images/2.jpg new file mode 100644 index 0000000..b1e9d65 Binary files /dev/null and b/images/2.jpg differ diff --git a/images/20.jpg b/images/20.jpg new file mode 100644 index 0000000..116b2d8 Binary files /dev/null and b/images/20.jpg differ diff --git a/images/3.jpg b/images/3.jpg new file mode 100644 index 0000000..264a6f5 Binary files /dev/null and b/images/3.jpg differ diff --git a/images/4.jpg b/images/4.jpg new file mode 100644 index 0000000..b95b37e Binary files /dev/null and b/images/4.jpg differ diff --git a/images/5.jpg b/images/5.jpg new file mode 100644 index 0000000..3addfcb Binary files /dev/null and b/images/5.jpg differ diff --git a/images/6.jpg b/images/6.jpg new file mode 100644 index 0000000..2c91e92 Binary files /dev/null and b/images/6.jpg differ diff --git a/images/7.jpg b/images/7.jpg new file mode 100644 index 0000000..a68c52d Binary files /dev/null and b/images/7.jpg differ diff --git a/images/8.jpg b/images/8.jpg new file mode 100644 index 0000000..98b5e8a Binary files /dev/null and b/images/8.jpg differ diff --git a/images/9.jpg b/images/9.jpg new file mode 100644 index 0000000..2e1bbf2 Binary files /dev/null and b/images/9.jpg differ diff --git a/images/images/10.jpg b/images/images/10.jpg new file mode 100644 index 0000000..eed0476 Binary files /dev/null and b/images/images/10.jpg differ diff --git a/images/images/2.jpg b/images/images/2.jpg new file mode 100644 index 0000000..b1e9d65 Binary files /dev/null and b/images/images/2.jpg differ diff --git a/images/images/3.jpg b/images/images/3.jpg new file mode 100644 index 0000000..264a6f5 Binary files /dev/null and b/images/images/3.jpg differ diff --git a/images/images/4.jpg b/images/images/4.jpg new file mode 100644 index 0000000..b95b37e Binary files /dev/null and b/images/images/4.jpg differ