import requests import aiohttp from aiogram import Bot, Dispatcher, types from aiogram.contrib.fsm_storage.memory import MemoryStorage from aiogram.dispatcher.filters.state import State, StatesGroup from aiogram.utils import executor import re from aiogram.types import ReplyKeyboardMarkup, KeyboardButton import asyncio from aiogram.dispatcher import FSMContext from aiogram import types from aiogram.dispatcher.filters import Command from datetime import datetime, timedelta import gspread from oauth2client.service_account import ServiceAccountCredentials import re from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo import logging from aiogram.types import ReplyKeyboardRemove TOKEN = '6476840022:AAFlOlYHCH4UKbGlOfILw8xcIUG0AD354X8' bot = Bot(token=TOKEN) storage = MemoryStorage() dp = Dispatcher(bot, storage=storage) # Кнопка для перезапуска restart_button = ReplyKeyboardMarkup(resize_keyboard=True) restart_button.add(KeyboardButton("Перезапустить бота")) MAX_ATTEMPTS = 3 # Максимальное количество попыток ввода кода TIMEOUT = 30 # Время в секундах # Словарь для хранения данных о пользователях user_data = {} last_bot_message_id = {} # Авторизация и доступ к таблице scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"] credentials = ServiceAccountCredentials.from_json_keyfile_name("botforclinic-436512-0c117dd103a8.json", scope) client = gspread.authorize(credentials) sheet = client.open("Пациенты клиники").sheet1 # Открытие таблицы, используйте название вашей таблицы # Словарь для хранения данных о сообщениях пользователей user_messages = {} # История состояний для каждого пользователя user_states = {} class Form(StatesGroup): telegram_id = State() existing_patient = State() phone_number = State() fio = State() birthday = State() verification_code = State() confirm_fio = State() confirm_phone_number = State() # Создаем состояния class QuestionForm(StatesGroup): waiting_for_question = State() from aiogram import types # Настройка логирования logging.basicConfig(level=logging.INFO) # Главное меню main_menu = InlineKeyboardMarkup(row_width=2).add( InlineKeyboardButton("🌍 Контакты", callback_data="menu_contacts"), InlineKeyboardButton("📞 Связь", callback_data="menu_contact") ) # Подменю "контакты" contacts_menu = InlineKeyboardMarkup(row_width=1).add( InlineKeyboardButton("▶️ Назад", callback_data="main_menu") ) # Меню связь contact_menu = InlineKeyboardMarkup(row_width=1).add( InlineKeyboardButton("💬 Отправить сообщение администратору Клиники", callback_data="contact_administrator"), InlineKeyboardButton("🛎 Заказать звонок от администратора Клиники", callback_data="order_call"), InlineKeyboardButton("▶️ Назад", callback_data="main_menu"), ) administrator_contact = InlineKeyboardMarkup(row_width=1).add( InlineKeyboardButton("✍🏼 Написать сообщение", callback_data="message_admin"), InlineKeyboardButton("▶️ Назад", callback_data="menu_contact"), InlineKeyboardButton("▶️ Главное меню", callback_data="main_menu") ) message_ad = InlineKeyboardMarkup(row_width=1).add( InlineKeyboardButton("▶️ Назад", callback_data="contact_administrator"), InlineKeyboardButton("▶️ Главное меню", callback_data="main_menu") ) call_order = InlineKeyboardMarkup(row_width=1).add( InlineKeyboardButton("📞 Позвонить мне", callback_data="call_me"), InlineKeyboardButton("▶️ Назад", callback_data="menu_contact"), InlineKeyboardButton("▶️ Главное меню", callback_data="main_menu") ) return_call_me = InlineKeyboardMarkup(row_width=1).add( InlineKeyboardButton("▶️ Назад", callback_data="order_call"), InlineKeyboardButton("▶️ Главное меню", callback_data="main_menu") ) whatsapp = InlineKeyboardMarkup(row_width=1).add( InlineKeyboardButton("▶️ Главное меню", callback_data="main_menu") ) # Обработка нажатий на инлайн-кнопки @dp.callback_query_handler(lambda c: c.data.startswith("menu_") or c.data == "main_menu") async def handle_main_menu(call: types.CallbackQuery): if call.data == "main_menu": await call.message.edit_text("В настоящее время я, как Ваш виртуальный помощник, активно развиваюсь." " В ближайшее время у меня появятся новые функции и возможности." " А пока Вы можете ознакомиться с текущим меню.", reply_markup=main_menu) elif call.data == "menu_contacts": await call.message.edit_text("Клиники им. проф. Е.Н.Оленевой\n\n" "Клиника УХО•ГОРЛО•НОС\n" "Записаться на приём, задать вопрос:\n" "📞 +7 (342) 207-03-03\n\n" "Больше информации о клинике и врачах на нашем 🔗САЙТЕ\n" "📱г. Пермь, ул. К. Цеткин, 9\n" "(ост. Строительный факультет)\n" "Построить маршрут\n\n" "--------------\n\n" "Клиника лечения КАШЛЯ•АЛЛЕРГИИ\n" "Записаться на приём, задать вопрос:\n" "📞 +7 (342) 200-02-03\n\n" "Больше информации о клинике и врачах на нашем 🔗САЙТЕ\n\n" "📱г. Пермь, ул. Пермь, ул. Г. Звезда, 31А\n" "(ост. Г. Звезда)\n" "Построить маршрут\n\n" "--------------\n\n" "Центр ДИАГНОСТИКИ И РЕАБИЛИТАЦИИ \n" "📞 +7 (342) 287-16-94\n\n" "Больше информации о клинике и врачах на нашем 🔗САЙТЕ\n\n" "📱 г. Пермь, ул. Пермь, ул. Г. Звезда, 31А\n" " (ост. Г. Звезда)\n" "Построить маршрут\n\n" "Присоединяйтесь к нам в Телеграм", reply_markup=contacts_menu, parse_mode="HTML") elif call.data == "menu_contact": await call.message.edit_text("Выберите способ связи", reply_markup=contact_menu) # Обработка нажатия на "Запись на исследование" @dp.callback_query_handler(lambda c: c.data == "contact_administrator") async def handle_call_menu(call: types.CallbackQuery): await call.message.edit_text("💬 Для отправки сообщения администратору Клиники нажмите кнопку ниже.", reply_markup=administrator_contact) # Обработка нажатия на "Запись на исследование" @dp.callback_query_handler(lambda c: c.data == "order_call") async def handle_admin_call_menu(call: types.CallbackQuery): await call.message.edit_text("✍🏼 Для получения обратного звонка от администратора Клиники нажмите кнопку ниже.", reply_markup=call_order) @dp.callback_query_handler(lambda c: c.data == "call_me") async def handle_ad_call_menu(call: types.CallbackQuery): user_id = str(call.from_user.id) # Преобразуем в строку для сравнения try: # Получение всех записей из таблицы records = sheet.get_all_records() # Поиск пользователя по Telegram ID user_data = next((entry for entry in records if str(entry['Telegram ID']) == user_id), None) if not user_data: await call.message.answer("Ваши данные не найдены.", reply_markup=return_call_me) return phone_number = user_data['Номер телефона'] # Измените на название вашего столбца # Отправка на сервер await send_message_server(phone_number, "Заказан звонок") # Ответ пользователю await call.message.edit_text("В ближайшее время с Вами свяжется администратор и проконсультирует по всем вопросам.", reply_markup=return_call_me) except Exception as e: logging.error(f"Ошибка при доступе к таблице: {e}") await call.message.edit_text("Произошла ошибка. Попробуйте снова позже.", reply_markup=return_call_me) # Обработчик кнопки "message_admin" @dp.callback_query_handler(lambda c: c.data == "message_admin") async def handle_ad_message(call: types.CallbackQuery, state: FSMContext): user_id = str(call.from_user.id) # Преобразуем в строку для сравнения try: # Получение всех записей из таблицы records = sheet.get_all_records() # Поиск пользователя по Telegram ID user_data = next((entry for entry in records if str(entry['Telegram ID']) == user_id), None) if not user_data: await call.message.answer("Ваши данные не найдены.", reply_markup=message_ad) return # Переход в состояние ожидания вопроса prompt_message = await call.message.edit_text( "Пожалуйста, отправьте ваш вопрос администратору одним сообщением.", reply_markup=message_ad ) # Сохраняем ID сообщения async with state.proxy() as data: data["prompt_message_id"] = prompt_message.message_id await QuestionForm.waiting_for_question.set() # Устанавливаем состояние ожидания except Exception as e: logging.error(f"Ошибка при доступе к таблице: {e}") await call.message.edit_text("Произошла ошибка. Попробуйте снова позже.", reply_markup=message_ad) # Обработчик ввода вопроса @dp.message_handler(state=QuestionForm.waiting_for_question, content_types=types.ContentTypes.TEXT) async def handle_user_question(message: types.Message, state: FSMContext): user_id = str(message.from_user.id) user_question = message.text try: # Удаление предыдущего сообщения async with state.proxy() as data: if "prompt_message_id" in data: await bot.delete_message(chat_id=message.chat.id, message_id=data["prompt_message_id"]) # Здесь можно отправить вопрос на сервер или обработать его await send_message_server(user_id, user_question) # Ответ пользователю await message.answer( "Ваше сообщение успешно отправлено! Администратор клиники ответит Вам в ближайшее время. Спасибо за Ваше обращение!", reply_markup=message_ad ) except Exception as e: logging.error(f"Ошибка при обработке вопроса: {e}") await message.answer( "Произошла ошибка при отправке вопроса. Попробуйте позже.", reply_markup=message_ad ) # Завершаем состояние await state.finish() # Хранилище для уже обработанных пользователей processed_users = set() # Функция проверки таблицы async def check_verification_status(): # while True: try: # Чтение всех строк таблицы records = sheet.get_all_records() for row in records: fio = row.get("ФИО") phone = row.get("Номер телефона") birth_date = row.get("Дата рождения") telegram_id = row.get("Telegram ID") timestamp = row.get("Время заполнения") verification_status = row.get("Верификация") # Проверяем, если "Верификация" = "Пройдена" и сообщение ещё не отправлено if ( verification_status and verification_status.strip().lower() == "пройдена" and telegram_id not in processed_users ): await send_success_message(telegram_id) processed_users.add(telegram_id) # Добавляем пользователя в обработанные return None except Exception as e: print(f"Ошибка при проверке верификации: {e}") # Интервал проверки (например, каждые 60 секунд) await asyncio.sleep(1) # Функция отправки сообщения async def send_success_message(telegram_id): try: await bot.send_message( chat_id=telegram_id, text=( "Ура! Ваша регистрация прошла успешно! 🥳\n\n" "Теперь все уведомления о записи на приём будут приходить сюда, в чат-бот. " "Если Вы также получаете рассылку в WhatsApp, то сообщения будут дублироваться.\n\n" "Для получения рассылки только в чат-бот Telegram, нажмите кнопку ниже." ), reply_markup=InlineKeyboardMarkup().add( InlineKeyboardButton("Не дублировать сообщения в WhatsApp", callback_data="stop_replying"), InlineKeyboardButton("Главное меню", callback_data="main_menu") ) ) except Exception as e: print(f"Ошибка при отправке сообщения пользователю {telegram_id}: {e}") # Обработка нажатия кнопок @dp.callback_query_handler(lambda c: c.data == "stop_replying") async def stop_replying_handler(call: types.CallbackQuery): user_id = str(call.from_user.id) # Преобразуем в строку для сравнения try: # Получение всех записей из таблицы records = sheet.get_all_records() # Поиск пользователя по Telegram ID user_data = next((entry for entry in records if str(entry['Telegram ID']) == user_id), None) if not user_data: await call.message.answer("Ваши данные не найдены.", reply_markup=whatsapp) return phone_number = user_data['Номер телефона'] # Измените на название вашего столбца # Отправка на сервер await send_message_server(phone_number, "Отменить рассылку в WhatsApp") # Ответ пользователю await call.message.edit_text("Рассылка в WhatsApp отключена.", reply_markup=whatsapp) except Exception as e: logging.error(f"Ошибка при доступе к таблице: {e}") await call.message.edit_text("Произошла ошибка. Попробуйте снова позже.", reply_markup=whatsapp) @dp.message_handler(commands=['help']) async def handle_help(message: types.Message): #await save_message_data(message) user_id = message.from_user.id help_message = "Список доступных команд:\n" help_message += "/start - начать взаимодействие с ботом\n" help_message += "/registration - регистрация\n" help_message += "/help - показать список команд и их описания\n" sent_message = await bot.send_message(user_id, help_message) last_bot_message_id[user_id] = sent_message.message_id @dp.message_handler(Command("start"), state="*") async def handle_start(message: types.Message, state: FSMContext): # Завершаем текущее состояние и очищаем данные пользователя await state.finish() # Завершает текущее состояние await state.storage.reset_data(user=message.from_user.id) # Удаляет все данные пользователя в FSM хранилище # Отправляем фото with open("s-blob-v1-IMAGE-tdNCrEv8Ldo.png", "rb") as photo: await bot.send_photo( chat_id=message.from_user.id, photo=photo, caption=("Добро пожаловать!\nЯ чат-бот Клиники и Ваш надежный виртуальный помощник.\nЧтобы узнать, что я могу для Вас сделать, просто введите /help\nДля начала работы нажмите кнопку /registration внизу."), reply_markup=generate_markup_registration() # Кнопка для продолжения ) @dp.message_handler(commands=['registration'], chat_type=types.ChatType.PRIVATE) async def handle_registration(message: types.Message): user_id = message.from_user.id sent_message = await bot.send_message(user_id, "Вы уже являетесь пациентом нашей Клиники?\nЕсли Вы хотите начать общение с ботом заново, нажмите /start.", reply_markup=yes_no_markup()) last_bot_message_id[user_id] = sent_message.message_id await Form.existing_patient.set() def generate_markup_telegram_id(): markup = types.ReplyKeyboardMarkup(row_width=2, resize_keyboard=True) item3 = types.KeyboardButton("Отправить Telegram ID") markup.add(item3) return markup def generate_markup_registration(): markup = types.ReplyKeyboardMarkup(row_width=1, resize_keyboard=True) item_registration = types.KeyboardButton("/registration") markup.add(item_registration) return markup @dp.message_handler(state=Form.existing_patient) async def handle_existing_patient(message: types.Message, state: FSMContext): user_id = message.from_user.id form_url = f"https://tgbotpolimed.pirogov.ai/?user_id={user_id}" if not message.text.strip(): await message.answer( "Пожалуйста, выберите 'Да' или 'Нет' с помощью кнопок.", reply_markup=ReplyKeyboardRemove() ) return if message.text.lower() == "да": await message.answer("В соответствии с Федеральным законом № 152-ФЗ «О персональных данных», для идентификации Вас как пациента нашей Клиники, просим заполнить форму, нажав на кнопку ниже.",reply_markup=ReplyKeyboardRemove()) await message.answer("Ввод персональных данных", reply_markup=InlineKeyboardMarkup().add( InlineKeyboardButton( text="Заполнить форму", web_app=WebAppInfo(url=form_url) ) ) ) await asyncio.create_task(check_verification_status()) await state.finish() elif message.text.lower() == "нет": await bot.send_message( user_id, "Полный доступ к функциям сервиса предоставляется только пациентам нашей Клиники.", reply_markup=ReplyKeyboardRemove() # Удаляем кнопки "Да"/"Нет" ) await state.finish() else: await bot.send_message( user_id, "Пожалуйста, выберите 'Да' или 'Нет' с помощью кнопок.", reply_markup=ReplyKeyboardRemove() # Удаляем кнопки "Да"/"Нет" ) await state.finish() def find_patients_by_id(telegram_id): records = sheet.get_all_records() # Получаем все строки таблицы patients = [] for record in records: if str(record["Telegram ID"]) == str(telegram_id): patients.append(record) return patients async def prompt_patient_selection(user_id, patient_records): # Создаем кнопки с ФИО пациентов markup = ReplyKeyboardMarkup(row_width=1, resize_keyboard=True) for record in patient_records: markup.add(KeyboardButton(record["ФИО"])) # Сохраняем данные пациентов временно user_data[user_id]["patients"] = patient_records await bot.send_message( user_id, "Найдено несколько записей с вашим Telegram ID. Пожалуйста, выберите пациента:", reply_markup=markup, ) await Form.fio.set() async def save_message_data(message: types.Message, reply_to_message_id=None): user_id = message.from_user.id reply_to_message_id = last_bot_message_id.get(user_id, None) user_messages[message.message_id] = { "message_id": message.message_id, "text": message.text, "date": message.date.isoformat(), # Исправлено "user_id": message.from_user.id, "reply_to_message_id": reply_to_message_id } print(f"Сообщение пользователя {user_id} сохранено: {user_messages[message.message_id]}") await send_message_to_server(user_messages[message.message_id]) async def send_message_to_server(message_data): url = "http://46.146.229.242:1980/AppSaveMessage" headers = {"Content-Type": "application/json"} async with aiohttp.ClientSession() as session: async with session.post(url, headers=headers, json=message_data) as response: if response.status == 200: print(f"Сообщение {message_data['message_id']} успешно отправлено на сервер.") else: print(f"Ошибка при отправке сообщения {message_data['message_id']} на сервер. Статус-код: {response.status}") # Функция для выполнения POST-запроса и получения списка врачей def get_doctors(): url = "http://46.146.229.242:1980/AppZaprSpecDoc" # Данные для отправки в запросе data = {"spec_id": 1} # Выполняем POST-запрос response = requests.post(url, json=data) # Проверяем статус ответа if response.status_code == 200: # Если запрос успешен, возвращаем данные return response.json() else: # Если запрос не удался, выводим ошибку print(f"Ошибка: {response.status_code}") return [] def yes_no_markup(): markup = types.ReplyKeyboardMarkup(row_width=2, resize_keyboard=True) item_no = types.KeyboardButton("Нет") item_yes = types.KeyboardButton("Да") markup.add(item_yes, item_no) return markup def markup2(): markup = types.ReplyKeyboardMarkup(row_width=2, resize_keyboard=True) item_good = types.KeyboardButton("Всё верно") item_no_good = types.KeyboardButton("Указать ФИО ещё раз") markup.add(item_good, item_no_good) return markup def generate_start_markup(): markup = types.ReplyKeyboardMarkup(row_width=1, resize_keyboard=True) start_button = types.KeyboardButton("/start") markup.add(start_button) return markup async def send_message_server(phone_number, text): url = "http://46.146.229.242:1980/AppSaveMessage" headers = {"Content-Type": "application/json"} message_data = { "phone_number": phone_number, "text": text } async with aiohttp.ClientSession() as session: async with session.post(url, headers=headers, json=message_data) as response: if response.status == 200: print("Сообщение успешно отправлено на сервер.") print(message_data) else: print(f"Ошибка при отправке сообщения на сервер. Статус-код: {response.status}") def find_patient_by_id(telegram_id): records = sheet.get_all_records() # Получаем все строки таблицы for record in records: if str(record["Telegram ID"]) == str(telegram_id): # Сравнение с Telegram ID phone_raw = record["Номер телефона"] # Удаляем все нецифровые символы phone_formatted = re.sub(r"\D", "", phone_raw) # Проверка на правильную длину номера и корректировка, если необходимо if len(phone_formatted) == 11: if phone_formatted.startswith("8"): # Если номер начинается с 8, заменяем на 7 phone_formatted = "7" + phone_formatted[1:] return phone_formatted else: return None # Возвращаем None, если номер неправильной длины return None def authorize_google(): # google_json = {} # for key in config["GOOGLE"].keys(): # google_json[key] = config["GOOGLE"][key] scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"] creds = ServiceAccountCredentials.from_json_keyfile_name("botforclinic-436512-0c117dd103a8.json", scope) # creds = ServiceAccountCredentials.from_json_keyfile_dict(google_json, scope) client = gspread.authorize(creds) return client @dp.message_handler(content_types=types.ContentType.TEXT) async def handle_message(message: types.Message): await save_message_data(message) if __name__ == '__main__': executor.start_polling(dp, skip_updates=True)