You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

676 lines
32 KiB

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 = '7907319434:AAEEM55xbWHhp5uPAlJ5AS2vNoleES_z_Kk'
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"
"Больше информации о клинике и врачах на нашем <a href='https://perm.oclinica.ru/lor'>🔗САЙТЕ</a>\n"
"📍г. Пермь, ул. К. Цеткин, 9\n"
"(ост. Строительный факультет)\n"
"<a href='https://tinyurl.com/286a8tv7'>Построить маршрут</a>\n\n"
"--------------\n\n"
"Клиника лечения КАШЛЯ•АЛЛЕРГИИ\n"
"Записаться на приём, задать вопрос:\n"
"📞 +7 (342) 200-02-03\n\n"
"Больше информации о клинике и врачах на нашем <a href='https://perm.oclinica.ru/allergo'>🔗САЙТЕ</a>\n\n"
"📍г. Пермь, ул. Пермь, ул. Г. Звезда, 31А\n"
"(ост. Г. Звезда)\n"
"<a href='https://tinyurl.com/9a9w5pdu'>Построить маршрут</a>\n\n"
"--------------\n\n"
"Центр ДИАГНОСТИКИ И РЕАБИЛИТАЦИИ \n"
"📞 +7 (342) 287-16-94\n\n"
"Больше информации о клинике и врачах на нашем <a href='https://cdr.oclinica.ru/'>🔗САЙТЕ</a>\n\n"
"📍 г. Пермь, ул. Пермь, ул. Г. Звезда, 31А\n"
" (ост. Г. Звезда)\n"
"<a href='https://tinyurl.com/9a9w5pdu'>Построить маршрут</a>\n\n"
"Присоединяйтесь к нашему каналу новостей в <a href='https://t.me/permlor'>Telegram</a>.", 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):
try:
user_id = call.from_user.id # ID пользователя
records = sheet.get_all_records()
# Получаем данные пользователя из таблицы
user_data = next((entry for entry in records if str(entry['Telegram ID']) == str(user_id)), None)
if not user_data:
await call.message.answer("Ваши данные не найдены.", reply_markup=return_call_me)
return
phone_number = user_data['Номер телефона']
text = "Позвонить мне"
message_id = call.message.message_id
date = call.message.date.isoformat() # Конвертация даты
reply_to_message_id = call.message.reply_to_message.message_id if call.message.reply_to_message else None
# Передаём данные на сервер
await send_message_server(phone_number, text, message_id, user_id, date, reply_to_message_id)
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) # ID пользователя
text = 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_server2(user_id, text, message.message_id, user_id, message.date.isoformat(), None)
# Ответ пользователю
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()
# Обработка нажатия на кнопку "Проверить данные"
@dp.callback_query_handler(lambda c: c.data == 'check_verification')
async def process_check_verification(callback_query: types.CallbackQuery, state: FSMContext):
user_id = callback_query.from_user.id
try:
# Чтение всех строк таблицы
records = sheet.get_all_records()
user_data_verified = False
for row in records:
fio = str(row.get("ФИО", "")).strip()
phone = str(row.get("Номер телефона", "")).strip()
telegram_id = str(row.get("Telegram ID", "")).strip()
# Проверяем, если в таблице есть данные для этого пользователя
if telegram_id == str(user_id):
user_data_verified = True
break
if user_data_verified:
await send_success_message(user_id) # Отправляем сообщение о успешной регистрации
else:
await bot.send_message(
user_id,
"Мы не нашли ваших данных в системе. Пожалуйста, проверьте, что вы правильно заполнили форму и попробуйте снова.",
reply_markup=InlineKeyboardMarkup().add(
InlineKeyboardButton("Попробовать снова", callback_data="check_verification")
)
)
except Exception as e:
print(f"Ошибка при проверке верификации: {e}")
await bot.send_message(user_id, "Произошла ошибка при проверке данных. Попробуйте позже.")
# Функция отправки сообщения об успешной регистрации
async def send_success_message(user_id):
try:
await bot.send_message(
chat_id=user_id,
text=("Ура! Ваша регистрация прошла успешно! 🥳\n\n"
"Теперь все уведомления о записи на приём будут приходить сюда, в чат-бот. "
"Если Вы также получаете рассылку в WhatsApp, то сообщения будут дублироваться. "
"Для получения рассылки только в чат-бот Telegram, нажмите кнопку ниже 'Не дублировать сообщения в WhatsApp'."),
reply_markup=InlineKeyboardMarkup().add(
InlineKeyboardButton("Не дублировать сообщения в WhatsApp", callback_data="stop_replying"),
InlineKeyboardButton("🔘 Главное меню", callback_data="main_menu")
)
)
except Exception as e:
print(f"Ошибка при отправке сообщения пользователю {user_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['Номер телефона'] # Измените на название вашего столбца
text = "Отключить дублирование в WhatsApp"
message_id = call.message.message_id
date = call.message.date.isoformat()
reply_to_message_id = call.message.reply_to_message.message_id if call.message.reply_to_message else None
# Отправка данных на сервер
await send_message_server(phone_number, text, message_id, call.from_user.id, date, reply_to_message_id)
# Ответ пользователю
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
def generate_back_button():
"""Создает клавиатуру с кнопкой 'Назад'."""
markup = ReplyKeyboardMarkup(row_width=1, resize_keyboard=True)
back_button = KeyboardButton("Назад") # Текст кнопки "Назад"
markup.add(back_button)
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 message.answer(
"После того как Вы заполните форму, нажмите на кнопку ниже для проверки данных.\n"
"Если Вы хотите начать общение с ботом заново, нажмите /start.",
reply_markup=InlineKeyboardMarkup().add(
InlineKeyboardButton(
text="Проверить данные",
callback_data="check_verification"
)
)
)
await state.finish()
elif message.text.lower() == "нет":
await bot.send_message(
user_id,
"Полный доступ к функциям сервиса предоставляется только пациентам нашей Клиники.\n\n"
"Вы можете связаться с нами по тел.: +7 (342) 207-03-03 или посетить Клинику лично для заключения договора по следующим адресам:\n"
"г. Пермь, ул. Клары Цеткин, д. 9; ул. Газеты Звезда, д. 31-а.\n\n"
"Будьте здоровы!\n"
"Если Вы хотите начать общение с ботом заново, нажмите /start.",
reply_markup=generate_back_button() # Клавиатура с кнопкой "Назад"
)
await state.finish()
else:
await bot.send_message(
user_id,
"Пожалуйста, выберите 'Да' или 'Нет' с помощью кнопок.",
reply_markup=ReplyKeyboardRemove()
)
await state.finish()
# Обработчик кнопки "Назад"
@dp.message_handler(lambda message: message.text == "Назад")
async def handle_back_button(message: types.Message):
await handle_registration(message) # Вызываем функцию обработки команды /registration
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://192.168.1.10:8080/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://192.168.1.10:8080/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, message_id, user_id, date, reply_to_message_id=None):
url = "http://192.168.1.10:8080/AppSaveMessage"
headers = {"Content-Type": "application/json"}
message_data = {
"message_id": message_id,
"text": text,
"date": date,
"user_id": user_id, # Передача ID пользователя
"reply_to_message_id": reply_to_message_id, # Если нужно указать reply_to_message_id
"phone_number": phone_number
}
async with aiohttp.ClientSession() as session:
async with session.post(url, json=message_data, headers=headers) as response:
if response.status == 200:
print(message_data)
logging.info("Сообщение успешно отправлено на сервер")
else:
logging.error(f"Ошибка при отправке сообщения: {response.status}")
async def send_message_server2(user_id, text, message_id, from_user_id, date, reply_to_message_id):
url = "http://192.168.1.10:8080/AppSaveMessage"
headers = {"Content-Type": "application/json"}
message_data = {
"user_id": user_id,
"text": text,
"message_id": message_id,
"from_user_id": from_user_id,
"date": date,
"reply_to_message_id": reply_to_message_id,
}
async with aiohttp.ClientSession() as session:
async with session.post(url, json=message_data, headers=headers) as response:
if response.status == 200:
logging.info("Сообщение успешно отправлено на сервер")
else:
logging.error(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)