|
|
@ -0,0 +1,676 @@ |
|
|
|
|
|
|
|
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) |