|
|
from googleapiclient.discovery import build |
|
|
from google.oauth2.service_account import Credentials |
|
|
import asyncio |
|
|
import logging |
|
|
from aiogram.types import ChatType |
|
|
from aiogram.utils.exceptions import BadRequest |
|
|
from aiogram.utils.exceptions import MessageCantBeDeleted |
|
|
import gspread |
|
|
from google.oauth2 import service_account |
|
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo, ReplyKeyboardMarkup, KeyboardButton |
|
|
import sentry_sdk |
|
|
from aiogram import Bot, Dispatcher, types |
|
|
from aiogram.contrib.fsm_storage.memory import MemoryStorage |
|
|
from aiogram.utils import executor |
|
|
from aiogram.dispatcher import FSMContext |
|
|
from aiogram.dispatcher.filters.state import StatesGroup, State |
|
|
import subprocess |
|
|
from googleapiclient.discovery import build |
|
|
from googleapiclient.errors import HttpError |
|
|
import os |
|
|
import json |
|
|
from googleapiclient.discovery import build |
|
|
from googleapiclient.errors import HttpError |
|
|
from datetime import datetime |
|
|
import subprocess |
|
|
from aiogram import types |
|
|
from aiogram.dispatcher import filters |
|
|
from aiogram.types import Message |
|
|
from datetime import datetime, timedelta |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 = 5522111920 # ID администратора, которому будет приходить уведомление |
|
|
ADMIN_IDS = [5522111920, 766945900] # Список ID администраторов |
|
|
SPREADSHEET_ID = '1j12H6NCZec9MEWQypPbzhETNtn58fnin1HBRkkr3a1w' |
|
|
SHEET_NAME_GROUPS = 'Лист2' |
|
|
SHEET_NAME_EMPLOYEES = 'Лист1' |
|
|
RANGE_NAME = 'Лист1!A1:O' # Диапазон данных в таблице |
|
|
CHAT_IDS = [-1002306913175, -1002266505101] # Замените на ID чатов, где бот администратор |
|
|
|
|
|
# Логирование |
|
|
logging.basicConfig(level=logging.INFO) |
|
|
|
|
|
bot = Bot(token=API_TOKEN, timeout=30) |
|
|
# Инициализация диспетчера и хранилища состояний |
|
|
storage = MemoryStorage() |
|
|
dp = Dispatcher(bot, storage=storage) |
|
|
|
|
|
# Настройки для 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' |
|
|
|
|
|
|
|
|
CHANGES_FILE = r"C:\Users\ilyac\PycharmProjects\Bot_for_clinic\sheets_backup\changes.json" |
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
|
|
|
|
|
|
# Google API настройки |
|
|
SCOPES = ['https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/drive'] |
|
|
SERVICE_ACCOUNT_FILE = 'botforclinic-436512-0c117dd103a8.json' |
|
|
|
|
|
credentials = Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES) |
|
|
sheets_service = build('sheets', 'v4', credentials=credentials) |
|
|
|
|
|
SPREADSHEET_NAME = "Корпоративные группы Клиники в Телеграмм" |
|
|
|
|
|
|
|
|
def load_changes(): |
|
|
"""Считываем данные из файла changes.json.""" |
|
|
with open(CHANGES_FILE, "r", encoding="utf-8") as f: |
|
|
return json.load(f) |
|
|
|
|
|
|
|
|
def get_sheet_data2(sheet_name): |
|
|
"""Получаем данные из листа по имени.""" |
|
|
spreadsheet = gc.open(SPREADSHEET_NAME) |
|
|
sheet = spreadsheet.worksheet(sheet_name) |
|
|
return sheet.get_all_values() |
|
|
|
|
|
|
|
|
def send_message_plan(sheet_data, start_date, tg_id): |
|
|
"""Создаем план отправки сообщений.""" |
|
|
messages_to_send = [] |
|
|
for row in sheet_data[1:]: # Пропускаем заголовок |
|
|
description = row[0] |
|
|
link = row[2] |
|
|
message = row[3] |
|
|
days_offset = int(row[4]) # Дни с начала периода |
|
|
send_time = row[5] |
|
|
|
|
|
# Рассчитываем дату и время отправки |
|
|
send_date = datetime.strptime(start_date, "%d.%m.%Y, %H:%M") + timedelta(days=days_offset) |
|
|
send_datetime = datetime.strptime(f"{send_date.date()} {send_time}", "%Y-%m-%d %H:%M") |
|
|
|
|
|
messages_to_send.append({ |
|
|
"datetime": send_datetime, |
|
|
"tg_id": tg_id, |
|
|
"message": f"{message}\n{link}", |
|
|
"description": description |
|
|
}) |
|
|
return messages_to_send |
|
|
|
|
|
|
|
|
async def send_messages(bot, messages_to_send, sheet_name): |
|
|
"""Отправляем сообщения и записываем время прочтения.""" |
|
|
spreadsheet = gc.open(SPREADSHEET_NAME) |
|
|
sheet = spreadsheet.worksheet(sheet_name) |
|
|
for msg in messages_to_send: |
|
|
# Ожидание времени отправки |
|
|
now = datetime.now() |
|
|
if now < msg["datetime"]: |
|
|
await asyncio.sleep((msg["datetime"] - now).total_seconds()) |
|
|
|
|
|
# Отправляем сообщение |
|
|
await bot.send_message(msg["tg_id"], msg["message"]) |
|
|
|
|
|
# Записываем время прочтения |
|
|
cell = sheet.find(msg["description"]) # Ищем описание в столбце A |
|
|
row = cell.row |
|
|
col = sheet.row_values(1).index(f"{msg['tg_id']}") + 1 # Столбец по ID |
|
|
sheet.update_cell(row, col, now.strftime("%d.%m.%Y %H:%M")) |
|
|
|
|
|
|
|
|
@dp.message_handler(commands=['rassil']) |
|
|
async def start_handler(message: Message): |
|
|
"""Обрабатываем команду /start.""" |
|
|
changes = load_changes2() |
|
|
for sheet_name, employees in changes.items(): |
|
|
for employee in employees: |
|
|
full_name = employee[0] |
|
|
tg_id = int(employee[13]) # ID Telegram |
|
|
start_date = employee[14] # Дата начала |
|
|
sheet_data = get_sheet_data2(sheet_name) |
|
|
|
|
|
# Создаем план отправки сообщений |
|
|
messages_to_send = send_message_plan(sheet_data, start_date, tg_id) |
|
|
|
|
|
# Отправляем сообщения |
|
|
await send_messages(bot, messages_to_send, sheet_name) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Известные сотрудники, чтобы избежать повторной обработки |
|
|
known_employees = set() |
|
|
|
|
|
|
|
|
def load_changes2(): |
|
|
"""Загружает изменения из файла changes.json.""" |
|
|
try: |
|
|
with open(r'C:\Users\ilyac\PycharmProjects\Bot_for_clinic\sheets_backup\changes.json', 'r', encoding='utf-8') as file: |
|
|
data = json.load(file) |
|
|
return data |
|
|
except Exception as e: |
|
|
logging.error(f"Ошибка при чтении файла changes.json: {e}") |
|
|
return {} |
|
|
|
|
|
|
|
|
def find_employee_in_sheet(full_name): |
|
|
"""Находит сотрудника в Google Таблице по ФИО.""" |
|
|
try: |
|
|
result = sheets_service.spreadsheets().values().get( |
|
|
spreadsheetId=SPREADSHEET_ID, range=RANGE_NAME |
|
|
).execute() |
|
|
values = result.get('values', []) |
|
|
|
|
|
for row in values: |
|
|
if row and row[0] == full_name: |
|
|
return row # Возвращаем строку с данными сотрудника |
|
|
return None |
|
|
except Exception as e: |
|
|
logging.error(f"Ошибка при поиске сотрудника в Google Таблице: {e}") |
|
|
return None |
|
|
|
|
|
|
|
|
def create_contract(employee_data): |
|
|
"""Создаёт трудовой договор на основе данных сотрудника.""" |
|
|
try: |
|
|
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" |
|
|
except Exception as e: |
|
|
logging.error(f"Ошибка при создании трудового договора: {e}") |
|
|
return None |
|
|
|
|
|
|
|
|
async def process_changes(): |
|
|
"""Обрабатывает изменения из файла changes.json.""" |
|
|
print('я тут') |
|
|
while True: |
|
|
changes = load_changes() |
|
|
for sheet_name, entries in changes.items(): |
|
|
for entry in entries: |
|
|
full_name = entry[0] |
|
|
if full_name in known_employees: |
|
|
continue # Пропускаем уже обработанных сотрудников |
|
|
|
|
|
employee_data = find_employee_in_sheet(full_name) |
|
|
if employee_data: |
|
|
contract_url = create_contract(employee_data) |
|
|
if contract_url: |
|
|
# Уведомляем администраторов |
|
|
for admin_id in ADMIN_IDS: |
|
|
await bot.send_message( |
|
|
admin_id, |
|
|
f"Новый сотрудник: {full_name}\n" |
|
|
f"Трудовой договор: {contract_url}" |
|
|
) |
|
|
|
|
|
known_employees.add(full_name) |
|
|
await asyncio.sleep(10) # Проверяем изменения каждые 10 секунд |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_admin(user_id): |
|
|
ADMIN_IDS = [5522111920, 766945900] # Список ID администраторов |
|
|
return user_id in ADMIN_IDS |
|
|
|
|
|
class GroupMessageState(StatesGroup): |
|
|
waiting_for_group_names1 = State() |
|
|
waiting_for_message_text = State() |
|
|
waiting_for_user_name = State() |
|
|
waiting_for_group_names = State() |
|
|
waiting_for_user_fio = State() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# # Словарь для хранения сотрудников, которых уже обработали |
|
|
# 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" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# |
|
|
# # Функция для отправки сообщения о новом сотруднике с клавиатурой для выбора специальности |
|
|
# 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'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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Получение данных из Google Таблицы |
|
|
def get_sheet_data(sheet_name): |
|
|
range_name = f"{sheet_name}!B1:C3" # Диапазон для строки с chat_id и группами |
|
|
result = service.spreadsheets().values().get( |
|
|
spreadsheetId=SPREADSHEET_ID, |
|
|
range=range_name |
|
|
).execute() |
|
|
return result.get('values', []) |
|
|
|
|
|
|
|
|
# Получение данных из Google Таблицы |
|
|
def get_sheet_data1(sheet_name): |
|
|
range_name = f"{sheet_name}!A1:P100" # Диапазон для строки с chat_id и группами |
|
|
result = service.spreadsheets().values().get( |
|
|
spreadsheetId=SPREADSHEET_ID, |
|
|
range=range_name |
|
|
).execute() |
|
|
return result.get('values', []) |
|
|
|
|
|
# Команда для рассылки сообщений в группы |
|
|
@dp.message_handler(commands=['send_to_groups'], chat_type=types.ChatType.PRIVATE) |
|
|
async def send_to_groups(message: types.Message): |
|
|
if not is_admin(message.from_user.id): |
|
|
await message.reply("Эта команда доступна только администратору.") |
|
|
return |
|
|
|
|
|
groups_data = get_sheet_data("Лист2") |
|
|
if len(groups_data) < 2: |
|
|
await message.reply("Данные о группах отсутствуют.") |
|
|
return |
|
|
|
|
|
await message.reply("Введите текст для рассылки в группы:") |
|
|
|
|
|
@dp.message_handler() |
|
|
async def broadcast_groups(msg: types.Message): |
|
|
text = msg.text |
|
|
groups_data = get_sheet_data("Лист2") # Укажите название листа в Google Sheets |
|
|
if not groups_data or len(groups_data) < 2: # Проверяем наличие данных |
|
|
await msg.reply("Данные о группах отсутствуют или некорректны.") |
|
|
return |
|
|
|
|
|
# Первая строка — chat_id (B1, C1, ...) |
|
|
chat_ids = groups_data[0] |
|
|
# Вторая строка — названия групп (B2, C2, ...) |
|
|
group_names = groups_data[1] |
|
|
|
|
|
for chat_id, group_name in zip(chat_ids, group_names): |
|
|
print(f"Chat ID: {chat_id}, Group: {group_name}") |
|
|
try: |
|
|
await bot.send_message(chat_id, text) |
|
|
except Exception as e: |
|
|
error_message = ( |
|
|
f"Не удалось отправить сообщение в группу {group_name} " |
|
|
f"(Chat ID: {chat_id}): {e}" |
|
|
) |
|
|
print(error_message) # Логируем ошибку |
|
|
await bot.send_message(ADMIN_ID, error_message) |
|
|
|
|
|
await msg.reply("Рассылка в группы завершена.") |
|
|
|
|
|
|
|
|
# Команда для рассылки сообщений сотрудникам |
|
|
@dp.message_handler(commands=['send_to_employees']) |
|
|
async def send_to_employees(message: types.Message): |
|
|
if not is_admin(message.from_user.id): |
|
|
await message.reply("Эта команда доступна только администратору.") |
|
|
return |
|
|
|
|
|
employees_data = get_sheet_data1("Лист1") |
|
|
if len(employees_data) < 2: |
|
|
await message.reply("Данные о сотрудниках отсутствуют.") |
|
|
return |
|
|
|
|
|
await message.reply("Введите текст для рассылки сотрудникам:") |
|
|
|
|
|
@dp.message_handler() |
|
|
async def broadcast_employees(msg: types.Message): |
|
|
text = msg.text |
|
|
for row in employees_data[1:]: |
|
|
if len(row) >= 14: # Проверяем наличие Telegram ID |
|
|
telegram_id = row[13] |
|
|
print(telegram_id) |
|
|
try: |
|
|
await bot.send_message(telegram_id, text) |
|
|
except Exception as e: |
|
|
await bot.send_message(ADMIN_ID, f"Не удалось отправить сообщение сотруднику {row[0]}: {e}") |
|
|
await msg.reply("Рассылка сотрудникам завершена.") |
|
|
|
|
|
|
|
|
@dp.message_handler(commands=['send_by_fio'], chat_type=types.ChatType.PRIVATE) |
|
|
async def send_by_fio(message: types.Message, state: FSMContext): # Добавляем state |
|
|
if not is_admin(message.from_user.id): |
|
|
await message.reply("Эта команда доступна только администратору.") |
|
|
return |
|
|
|
|
|
employees_data = get_sheet_data1("Лист1") |
|
|
if len(employees_data) < 2: |
|
|
await message.reply("Данные о сотрудниках отсутствуют.") |
|
|
return |
|
|
|
|
|
await message.reply("Введите список ФИО сотрудников через запятую (например, Иванов Иван Иванович, Петров Петр Петрович):") |
|
|
await state.set_state("waiting_for_fio_list") |
|
|
|
|
|
|
|
|
|
|
|
@dp.message_handler(state="waiting_for_fio_list") |
|
|
async def process_fio_list(message: types.Message, state: FSMContext): |
|
|
fio_list = [fio.strip() for fio in message.text.split(",")] |
|
|
employees_data = get_sheet_data1("Лист1") |
|
|
|
|
|
# Поиск сотрудников по ФИО |
|
|
found_employees = [] |
|
|
not_found_employees = [] |
|
|
|
|
|
for fio in fio_list: |
|
|
target_row = next((row for row in employees_data[1:] if row[0] == fio), None) |
|
|
if target_row and len(target_row) >= 14: # Проверяем наличие Telegram ID |
|
|
found_employees.append((fio, target_row[13])) # Добавляем (ФИО, Telegram ID) |
|
|
else: |
|
|
not_found_employees.append(fio) |
|
|
|
|
|
# Сохраняем найденных сотрудников в состояние |
|
|
await state.update_data(found_employees=found_employees) |
|
|
if not_found_employees: |
|
|
await message.reply( |
|
|
f"Следующие сотрудники не найдены или у них отсутствует Telegram ID: {', '.join(not_found_employees)}" |
|
|
) |
|
|
await message.reply("Введите текст сообщения для отправки сотрудникам:") |
|
|
await state.set_state("waiting_for_message_text") |
|
|
|
|
|
|
|
|
@dp.message_handler(state="waiting_for_message_text") |
|
|
async def send_messages_to_employees(message: types.Message, state: FSMContext): |
|
|
user_data = await state.get_data() |
|
|
found_employees = user_data.get("found_employees", []) |
|
|
text = message.text.strip() |
|
|
|
|
|
if not found_employees: |
|
|
await message.reply("Нет сотрудников для отправки сообщений.") |
|
|
await state.finish() |
|
|
return |
|
|
|
|
|
for fio, telegram_id in found_employees: |
|
|
try: |
|
|
await bot.send_message(telegram_id, text) |
|
|
await message.reply(f"Сообщение успешно отправлено сотруднику: {fio}") |
|
|
except Exception as e: |
|
|
await message.reply(f"Не удалось отправить сообщение сотруднику {fio}: {e}") |
|
|
|
|
|
await message.reply("Рассылка завершена.") |
|
|
await state.finish() |
|
|
|
|
|
|
|
|
@dp.message_handler(commands=['send_to_group'], chat_type=types.ChatType.PRIVATE) |
|
|
async def send_to_group(message: types.Message, state: FSMContext): |
|
|
if not is_admin(message.from_user.id): |
|
|
await message.reply("Эта команда доступна только администратору.") |
|
|
return |
|
|
|
|
|
await message.reply("Введите названия групп через запятую (например, Группа1, Группа2):") |
|
|
await state.set_state(GroupMessageState.waiting_for_group_names1) |
|
|
|
|
|
|
|
|
@dp.message_handler(state=GroupMessageState.waiting_for_group_names1) |
|
|
async def process_group_names(message: types.Message, state: FSMContext): |
|
|
group_names = [group.strip() for group in message.text.split(',') if group.strip()] |
|
|
|
|
|
# Читаем данные из Лист2 |
|
|
sheet_data = get_sheet_data1("Лист2") # Подключите функцию чтения данных |
|
|
|
|
|
if len(sheet_data) < 3 or len(sheet_data[0]) != len(sheet_data[1]): |
|
|
await message.reply("Ошибка в данных Google Таблицы. Проверьте формат.") |
|
|
return |
|
|
|
|
|
# Создаём словарь {название группы: chat_id} |
|
|
group_dict = {sheet_data[1][i]: sheet_data[0][i] for i in range(len(sheet_data[0]))} |
|
|
|
|
|
# Ищем chat_id для введённых названий групп |
|
|
group_ids = [] |
|
|
invalid_groups = [] |
|
|
for group in group_names: |
|
|
if group in group_dict: |
|
|
group_ids.append(group_dict[group]) |
|
|
else: |
|
|
invalid_groups.append(group) |
|
|
|
|
|
if not group_ids: |
|
|
await message.reply("Указанные группы не найдены в таблице.") |
|
|
return |
|
|
|
|
|
# Сохраняем список найденных chat_id в состояние |
|
|
await state.update_data(group_ids=group_ids, invalid_groups=invalid_groups) |
|
|
await message.reply( |
|
|
f"Группы найдены: {', '.join(group_names)}.\n\n" |
|
|
"Теперь введите текст сообщения для отправки:" |
|
|
) |
|
|
await state.set_state(GroupMessageState.waiting_for_message_text) |
|
|
|
|
|
|
|
|
@dp.message_handler(state=GroupMessageState.waiting_for_message_text) |
|
|
async def process_group_message(message: types.Message, state: FSMContext): |
|
|
text = message.text.strip() |
|
|
data = await state.get_data() |
|
|
group_ids = data.get('group_ids', []) |
|
|
invalid_groups = data.get('invalid_groups', []) |
|
|
|
|
|
for chat_id in group_ids: |
|
|
try: |
|
|
await bot.send_message(chat_id, text) |
|
|
except Exception as e: |
|
|
await message.reply(f"Не удалось отправить сообщение в группу {chat_id}: {e}") |
|
|
|
|
|
if invalid_groups: |
|
|
await message.reply(f"Следующие группы не найдены в таблице: {', '.join(invalid_groups)}") |
|
|
|
|
|
await message.reply("Сообщение успешно отправлено в найденные группы.") |
|
|
await state.finish() |
|
|
|
|
|
|
|
|
|
|
|
@dp.message_handler(commands=['add_to_groups'], chat_type=types.ChatType.PRIVATE) |
|
|
async def add_to_groups(message: types.Message, state: FSMContext): |
|
|
if not is_admin(message.from_user.id): |
|
|
await message.reply("Эта команда доступна только администратору.") |
|
|
return |
|
|
|
|
|
await message.reply("Введите названия групп через запятую (например, Группа1, Группа2):") |
|
|
await state.set_state(GroupMessageState.waiting_for_group_names) |
|
|
|
|
|
|
|
|
@dp.message_handler(state=GroupMessageState.waiting_for_group_names) |
|
|
async def process_group_names(message: types.Message, state: FSMContext): |
|
|
group_names = [group.strip() for group in message.text.split(',') if group.strip()] |
|
|
|
|
|
# Читаем данные из Лист2 |
|
|
sheet_data = get_sheet_data("Лист2") |
|
|
|
|
|
if len(sheet_data) < 3 or len(sheet_data[0]) != len(sheet_data[1]): |
|
|
await message.reply("Ошибка в данных Google Таблицы. Проверьте формат.") |
|
|
return |
|
|
|
|
|
# Создаём словарь {название группы: chat_id} |
|
|
group_dict = {sheet_data[1][i]: sheet_data[0][i] for i in range(len(sheet_data[0]))} |
|
|
|
|
|
# Ищем chat_id для введённых названий групп |
|
|
group_ids = [] |
|
|
invalid_groups = [] |
|
|
for group in group_names: |
|
|
if group in group_dict: |
|
|
group_ids.append(group_dict[group]) |
|
|
else: |
|
|
invalid_groups.append(group) |
|
|
|
|
|
if not group_ids: |
|
|
await message.reply("Указанные группы не найдены в таблице.") |
|
|
return |
|
|
|
|
|
# Сохраняем список найденных chat_id в состояние |
|
|
await state.update_data(group_ids=group_ids, invalid_groups=invalid_groups) |
|
|
await message.reply( |
|
|
f"Группы найдены: {', '.join(group_names)}.\n\n" |
|
|
"Теперь введите ФИО пользователя, которого нужно добавить в группы:" |
|
|
) |
|
|
await state.set_state(GroupMessageState.waiting_for_user_fio) |
|
|
|
|
|
|
|
|
@dp.message_handler(state=GroupMessageState.waiting_for_user_fio) |
|
|
async def process_user_fio(message: types.Message, state: FSMContext): |
|
|
user_fio = message.text.strip() |
|
|
|
|
|
# Читаем данные из Лист1 для поиска пользователя |
|
|
user_data = get_sheet_data1("Лист1") |
|
|
|
|
|
# Создаём словарь {ФИО: Telegram ID} |
|
|
user_dict = {row[0]: row[13] for row in user_data if len(row) > 13 and row[0] and row[13]} |
|
|
|
|
|
if user_fio not in user_dict: |
|
|
await message.reply("Пользователь с указанным ФИО не найден в таблице.") |
|
|
return |
|
|
|
|
|
user_id = user_dict[user_fio] |
|
|
data = await state.get_data() |
|
|
group_ids = data.get('group_ids', []) |
|
|
invalid_groups = data.get('invalid_groups', []) |
|
|
|
|
|
# Добавляем пользователя в группы и отправляем ссылки на приглашение |
|
|
for chat_id in group_ids: |
|
|
try: |
|
|
invite_link = await bot.create_chat_invite_link(chat_id) |
|
|
await bot.send_message(user_id, f"Вас приглашают в группу: {invite_link.invite_link}") |
|
|
except Exception as e: |
|
|
await message.reply(f"Не удалось добавить пользователя в группу {chat_id}: {e}") |
|
|
|
|
|
if invalid_groups: |
|
|
await message.reply(f"Следующие группы не найдены в таблице: {', '.join(invalid_groups)}") |
|
|
|
|
|
await message.reply("Пользователь успешно добавлен в найденные группы и получил ссылки на приглашения.") |
|
|
await state.finish() |
|
|
|
|
|
|
|
|
# Команда /update_sheets |
|
|
@dp.message_handler(commands=["update_sheets"]) |
|
|
async def update_sheets_command(message: types.Message): |
|
|
if not is_admin(message.from_user.id): |
|
|
await message.reply("Эта команда доступна только администратору.") |
|
|
return |
|
|
|
|
|
await message.reply("Процесс обновления данных начат...") |
|
|
try: |
|
|
result = subprocess.run( |
|
|
[ |
|
|
r"C:\Users\ilyac\PycharmProjects\Bot_for_clinic\.venv\Scripts\python.exe", |
|
|
r"C:\Users\ilyac\PycharmProjects\Bot_for_clinic\backup_google_sheets.py" |
|
|
], |
|
|
capture_output=True, |
|
|
text=True, |
|
|
encoding="utf-8", # Указываем кодировку |
|
|
check=True, |
|
|
) |
|
|
|
|
|
if result.returncode == 0: |
|
|
await message.reply(f"Обновление данных завершено успешно.") |
|
|
else: |
|
|
await message.reply(f"Обновление завершилось с ошибками:\n{result.stderr}") |
|
|
|
|
|
except subprocess.CalledProcessError as e: |
|
|
await message.reply(f"Ошибка выполнения скрипта: {e.stderr}") |
|
|
|
|
|
except Exception as e: |
|
|
await message.reply(f"Произошла ошибка: {e}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
loop = asyncio.get_event_loop() |
|
|
loop.create_task(process_changes()) |
|
|
from aiogram import executor |
|
|
executor.start_polling(dp, skip_updates=True) |