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.

443 lines
22 KiB

from googleapiclient.discovery import build
from google.oauth2.service_account import Credentials
import asyncio
from aiogram.types import WebAppInfo
import logging
from aiogram import Bot, Dispatcher, types
from aiogram.utils import executor
from aiogram.types import ChatType
from aiogram.utils.exceptions import BadRequest
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton
from aiogram.utils.exceptions import MessageCantBeDeleted
import gspread
from datetime import datetime
from google.oauth2 import service_account
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo, ReplyKeyboardMarkup, KeyboardButton
import sentry_sdk
sentry_sdk.init(
dsn="https://697d3f2b6cb54a78202eeae0cb8c1f65@sentry.pirogov.ai/15",
# Set traces_sample_rate to 1.0 to capture 100%
# of transactions for performance monitoring.
traces_sample_rate=1.0,
)
# Настройки бота
API_TOKEN = '7414018470:AAGYF77aCd1BQpRnf38ys05ijkYJ25sNBuU'
ADMIN_ID = 766945900 # ID администратора, которому будет приходить уведомление
SPREADSHEET_ID = '128R3An6fb2PFGlzYMKb4SvEaSYCWUHHFVvial7llGno' # ID вашей Google Таблицы
RANGE_NAME = 'Лист1!A1:O' # Диапазон данных в таблице
CHAT_IDS = [-1002306913175, -1002266505101] # Замените на ID чатов, где бот администратор
# Логирование
logging.basicConfig(level=logging.INFO)
bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
# Настройки для Google Sheets API
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
SERVICE_ACCOUNT_FILE = 'botforclinic-436512-0c117dd103a8.json'
credentials = Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = build('sheets', 'v4', credentials=credentials)
# Настройка API для работы с документами
SCOPES = ['https://www.googleapis.com/auth/documents', 'https://www.googleapis.com/auth/drive']
SERVICE_ACCOUNT_FILE = 'botforclinic-436512-0c117dd103a8.json'
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
docs_service = build('docs', 'v1', credentials=credentials)
drive_service = build('drive', 'v3', credentials=credentials)
# Идентификатор Google Документа (шаблона трудового договора)
TEMPLATE_DOC_ID = '1xi4YiPNBEDvODS0SaVb0wgKZ_b_OBkas'
# Словарь для хранения сотрудников, которых уже обработали
known_employees = set()
def create_contract_google(employee_data):
# Копирование шаблона
file_metadata = {
'name': f"ТД_{employee_data[0]}_{datetime.now().strftime('%Y%m%d%H%M%S')}",
'mimeType': 'application/vnd.google-apps.document',
'parents': ['1fyA5btnwCQC-juE40T-uLSVHVIB-I2Ny']
}
copied_file = drive_service.files().copy(
fileId=TEMPLATE_DOC_ID, body=file_metadata).execute()
document_id = copied_file['id']
# Замены текста в шаблоне
replacements = {
"{{ФИО}}": employee_data[0],
"{{Серия паспорта}}": employee_data[3],
"{{Номер паспорта}}": employee_data[4],
"{{Кем выдан}}": employee_data[5],
"{{Дата выдачи}}": employee_data[6],
"{{Адрес}}": employee_data[8],
"{{Дата рождения}}": employee_data[12],
"{{ИНН}}": employee_data[11],
"{{СНИЛС}}": employee_data[10],
}
requests = [{"replaceAllText": {
"containsText": {"text": key, "matchCase": True},
"replaceText": value
}} for key, value in replacements.items()]
docs_service.documents().batchUpdate(
documentId=document_id, body={'requests': requests}).execute()
return f"https://docs.google.com/document/d/{document_id}/edit"
# Словарь с соответствием специальности и ссылки на чат
specialty_chat_links = {
"Врачи": "https://t.me/botclinic",
"Медсёстры": "https://t.me/+DcZn0yBO0RFjZTY6",
"Санитарки": "https://t.me/joinchat/{chat_id_orderlies}",
"Администраторы": "https://t.me/joinchat/{chat_id_admins}",
"Операторы КЦ": "https://t.me/joinchat/{chat_id_operators}",
"АУП": "https://t.me/joinchat/{chat_id_aap}"
}
# Функция для отправки сообщения о новом сотруднике с клавиатурой для выбора специальности
async def notify_admin_about_new_employee(entry):
full_name = entry[0] if entry else "Без имени"
new_employee_id = entry[13] if len(entry) > 13 else None
if new_employee_id:
# Отправляем уведомление админу о новом сотруднике с клавишами выбора специальности
keyboard = create_employee_keyboard()
await bot.send_message(
ADMIN_ID,
f"Зарегистрирован новый сотрудник: {full_name}\nTelegram ID сотрудника: {new_employee_id}",
reply_markup=keyboard
)
else:
logging.error("Не удалось найти Telegram ID сотрудника.")
# Функция для проверки новых сотрудников
async def check_for_new_entries():
sheet = service.spreadsheets()
while True:
try:
result = sheet.values().get(spreadsheetId=SPREADSHEET_ID, range=RANGE_NAME).execute()
values = result.get('values', [])
except Exception as e:
logging.error(f"Ошибка при чтении таблицы: {e}")
await asyncio.sleep(30) # Ждем перед следующей попыткой
continue
# Проверяем каждую запись на наличие нового сотрудника
for entry in values:
full_name = entry[0] if entry else "Без имени"
new_employee_id = entry[13] if len(entry) > 13 else None # Telegram ID сотрудника
if full_name and full_name not in known_employees:
known_employees.add(full_name)
# Генерация трудового договора
try:
contract_url = create_contract_google(entry)
# Уведомление для администратора
await bot.send_message(
ADMIN_ID,
f"Зарегистрирован новый сотрудник: {full_name}\nТрудовой договор: {contract_url}",
)
# Уведомление для сотрудника
if new_employee_id:
welcome_message = f"Большое спасибо! Мы получили данные для заключения трудового договора с вами."
await bot.send_message(
new_employee_id,
welcome_message,
)
# Оповещаем админа и отправляем клавиатуру для выбора специальности
await notify_admin_about_new_employee(entry)
except Exception as e:
logging.error(f"Ошибка при создании трудового договора: {e}")
await bot.send_message(ADMIN_ID, "Произошла ошибка при создании трудового договора.")
await asyncio.sleep(10) # Проверяем таблицу каждые 10 секунд
# Функция отправки сообщения о прохождении инструктажа
async def send_fire_safety_instructions(employee_id, full_name, employee_data):
message_text = f"""
{full_name}, добрый день. Вы приглашены на корпоративный канал Клиники в Telegram. В закрепленном сообщении на этом канале Вы сможете ознакомиться с такими разделами:
👉[Наши сотрудники](https://t.me/c/1414064442/5) - актуальный список всех сотрудников Клиники;
👉[Контакты сотрудников](https://t.me/c/1414064442/39) - номера телефонов сотрудников для скачивания в телефонную книгу;
👉[История клиники](https://t.me/c/1414064442/6)
👉[Структура рабочих чатов](https://t.me/c/1414064442/10)
👉[Инструктаж по пожарной безопасности в Клинике ухо, горло, нос](https://disk.yandex.ru/i/i1k41Z2SznfpOA)
👉[Обратная связь](https://forms.gle/Dfjrb8K1NzzjmW5j7) - оставить предложение, пожелание или задать вопрос руководству (анонимно).
**ВАЖНО:**
В разделе [Инструктаж по пожарной безопасности в Клинике ухо, горло, нос](https://disk.yandex.ru/i/i1k41Z2SznfpOA) Вам необходимо изучить материалы по Пожарной безопасности, уделить особое внимание теме - порядок действий и по каким номерам звонить в случае пожароопасной ситуации.
После этого пройти тестирование по данной теме (21 вопрос - зачет 70% правильных ответов):
👉[Тестирование по пожарной безопасности](https://forms.gle/VLEx2Gf1h8grpXXu5)
**Срок обучения и прохождения тестирования** - до начала выполнения трудовой функции!
Спасибо!
С уважением,
Жуланова Наталья Александровна,
помощник генерального директора ООО "Клиника ухо, горло, нос им. проф. Е.Н.Оленевой" (г. Пермь)
📞 +7 (902) 64-54-648
"""
try:
# Отправка сообщения сотруднику
await bot.send_message(employee_id, message_text, parse_mode="Markdown")
logging.info(f"Сообщение о пожарной безопасности отправлено {full_name}.")
except Exception as e:
logging.error(f"Ошибка при отправке инструктажа или документа: {e}")
await bot.send_message(ADMIN_ID, "Произошла ошибка при отправке инструктажа или документа.")
# Обработчик нажатия на кнопки выбора специальности
@dp.message_handler(lambda message: message.text in specialty_chat_links.keys(), chat_type=types.ChatType.PRIVATE)
async def handle_specialty_selection(message: types.Message):
selected_specialty = message.text
data = sheet.get_all_values()
last_employee_row = len(data)
if last_employee_row < 2:
await message.answer("В таблице пока нет сотрудников для назначения специальности.")
return
new_employee_id = data[last_employee_row - 1][13]
full_name = data[last_employee_row - 1][0]
if not new_employee_id:
await message.answer("Не удалось найти Telegram ID сотрудника в таблице.")
return
try:
sheet.update_cell(last_employee_row, 16, selected_specialty)
await message.answer(f"Специальность сотрудника обновлена на: {selected_specialty}")
chat_invite_link = specialty_chat_links.get(selected_specialty)
if chat_invite_link:
await bot.send_message(
new_employee_id,
f"Поздравляем! Вы были добавлены в нашу команду как {selected_specialty}. Вот ссылка на вашу беседу: {chat_invite_link}"
)
await message.answer(f"Ссылка на беседу отправлена сотруднику.")
# Отправка сообщения о прохождении инструктажа
await send_fire_safety_instructions(new_employee_id, full_name, data[last_employee_row - 1])
except Exception as e:
await message.answer(f"Ошибка при обновлении таблицы: {e}")
# Функция для генерации клавиатуры с кнопками типа сотрудника
def create_employee_keyboard():
keyboard = ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=False)
button_doctors = KeyboardButton("Врачи")
button_nurses = KeyboardButton("Медсёстры")
button_orderlies = KeyboardButton("Санитарки")
button_admins = KeyboardButton("Администраторы")
button_operators = KeyboardButton("Операторы КЦ")
button_aap = KeyboardButton("АУП")
keyboard.add(button_doctors, button_nurses)
keyboard.add(button_orderlies, button_admins)
keyboard.add(button_operators, button_aap)
return keyboard
# Обработчик команды /start
@dp.message_handler(commands=['start'], chat_type=types.ChatType.PRIVATE)
async def send_welcome(message: types.Message):
user_id = message.from_user.id
# Отправка фото
photo_path = r'D:\Users\Maxim\PycharmProjectsbot\Bot_for_clinic\start_foto.png'
await bot.send_photo(user_id, types.InputFile(photo_path))
# Отправка текста с кнопкой
await bot.send_message(
user_id,
"Добрый день, этот бот нужен Вам для начала прохождения процедуры трудоустройства в "
"Общество с ограниченной ответственностью «Клиника ухо, горло, нос имени профессора Е.Н. Оленевой».\n"
"Нажмите, пожалуйста, на команду 👉 /registration",
reply_markup=get_registration_button()
)
# Обработчик команды регистрации
@dp.message_handler(commands=['registration'], chat_type=types.ChatType.PRIVATE)
async def send_welcome(message: types.Message):
await message.answer(
"В соответствии с требованиями статьи 9 Федерального закона от 27.07.2006 г. «О персональных данных» № 152-ФЗ даю согласие на обработку своих персональных данных.\n"
"Наименование и адрес оператора, получающего согласие субъекта персональных данных: Общество с ограниченной ответственностью «Клиника ухо, горло, нос имени профессора Е.Н. Оленевой».\n"
"Цель обработки персональных данных: организация труда и осуществление иных, связанных с этим мероприятий.",
reply_markup=get_consent_keyboard()
)
# Обработчик ответа пользователя
@dp.message_handler(lambda message: message.text in ["✅ Даю согласие на обработку своих персональных данных", "❌ Не даю согласие на обработку своих персональных данных"])
async def process_consent(message: types.Message):
user_id = message.from_user.id
if message.text == "✅ Даю согласие на обработку своих персональных данных":
form_url = f"https://tgbotkugn.pirogov.ai/?user_id={user_id}"
form_keyboard = InlineKeyboardMarkup().add(
InlineKeyboardButton(
text="Ввести персональные данные", web_app=WebAppInfo(url=form_url)
)
)
await message.answer(
"Спасибо за согласие! Нажмите кнопку ниже, чтобы ввести персональные данные:",
reply_markup=form_keyboard
)
else:
await message.answer(
"Вы не дали согласие на обработку персональных данных.\n"
"Вы не можете продолжить регистрацию.",
reply_markup=get_consent_keyboard()
)
@dp.message_handler(commands=['getchatid'], chat_type=[ChatType.GROUP, ChatType.SUPERGROUP])
async def get_chat_id(message: types.Message):
# Проверяем статус пользователя в чате
chat_member = await bot.get_chat_member(message.chat.id, message.from_user.id)
if chat_member.status not in ['administrator', 'creator']:
# Отправляем предупреждение о недостатке прав
warning_message = await message.answer("У вас нет прав на выполнение этой команды.")
# Удаляем команду /getchatid через 1 секунду
await asyncio.sleep(1)
try:
await message.delete()
except MessageCantBeDeleted:
logging.warning("Не удалось удалить сообщение с командой /getchatid")
# Удаляем предупреждение через 5 секунд
await asyncio.sleep(5)
try:
await warning_message.delete()
except MessageCantBeDeleted:
logging.warning("Не удалось удалить сообщение с предупреждением")
return
# Отправляем ID и название чата администратору бота
chat_id = message.chat.id
chat_title = message.chat.title or "Без названия"
await bot.send_message(ADMIN_ID, f"ID чата: {chat_id}\nНазвание чата: {chat_title}")
# Удаляем команду /getchatid через 1 секунду
await asyncio.sleep(1)
try:
await message.delete()
except MessageCantBeDeleted:
logging.warning("Не удалось удалить сообщение с командой /getchatid")
# Инициализация доступа к Google Sheets
gc = gspread.service_account(filename=SERVICE_ACCOUNT_FILE)
sheet = gc.open_by_key(SPREADSHEET_ID).sheet1 # Открываем первую таблицу
@dp.message_handler(commands=['kick'], chat_type=types.ChatType.PRIVATE)
async def kick_user(message: types.Message):
if message.from_user.id != ADMIN_ID:
await message.answer("Эта команда доступна только администратору бота.")
return
# Получаем ФИО из сообщения
full_name = message.get_args().strip()
if not full_name:
await message.answer("Пожалуйста, укажите ФИО пользователя для кика.")
return
# Поиск ID пользователя по ФИО в Google Таблице
try:
data = sheet.get_all_values() # Получаем все значения в таблице
except Exception as e:
await message.answer("Не удалось получить данные из таблицы.")
return
user_id = None
for row in data:
if row and row[0] == full_name: # ФИО в первом столбце
user_id = row[13] # ID в 14-м столбце (индекс 13)
break
if user_id is None:
await message.answer(f"Пользователь с ФИО '{full_name}' не найден в таблице.")
return
errors = [] # Список для хранения ошибок
for chat_id in CHAT_IDS:
try:
await bot.kick_chat_member(chat_id, user_id)
except BadRequest as e:
errors.append(f"Ошибка в чате {chat_id}: {e}")
# Отправляем сообщение админу один раз
if errors:
await bot.send_message(
ADMIN_ID,
f"Пользователь с ID {user_id} был исключён из чатов, "
f"но произошли ошибки: {'; '.join(errors)}"
)
else:
await bot.send_message(ADMIN_ID, f"Пользователь с ID {user_id} был исключён из всех чатов.")
await message.answer(f"Команда на кик пользователя с ФИО '{full_name}' отправлена во все чаты.")
def get_registration_button():
keyboard = ReplyKeyboardMarkup(resize_keyboard=True)
registration_button = KeyboardButton("/registration")
keyboard.add(registration_button)
return keyboard
def get_consent_keyboard():
keyboard = ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True)
consent_button = KeyboardButton("✅ Даю согласие на обработку своих персональных данных")
deny_button = KeyboardButton("❌ Не даю согласие на обработку своих персональных данных")
keyboard.add(consent_button, deny_button)
return keyboard
if __name__ == '__main__':
# Запускаем отслеживание таблицы и бота
loop = asyncio.get_event_loop()
loop.create_task(check_for_new_entries())
executor.start_polling(dp, skip_updates=True)