Автоматическая загрузка файлов из Email и загрузка на сайт ТКП
This commit is contained in:
403
email_processor.py
Normal file
403
email_processor.py
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
from imapclient import IMAPClient
|
||||||
|
import email
|
||||||
|
from email.header import decode_header
|
||||||
|
from pathlib import Path
|
||||||
|
import smtplib
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
from selenium.webdriver.support.ui import Select
|
||||||
|
from datetime import datetime, timedelta, date
|
||||||
|
import pyautogui
|
||||||
|
import time, sys
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
from telegram import Bot
|
||||||
|
import asyncio
|
||||||
|
import traceback
|
||||||
|
from selenium.webdriver.common.action_chains import ActionChains
|
||||||
|
|
||||||
|
# Настройка ведения журнала
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler('/home/specit/email_processor.log'),
|
||||||
|
logging.StreamHandler()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Конфигурация Telegram
|
||||||
|
TELEGRAM_BOT_TOKEN = '5374522720:AAGoNUYCEyJ-7-bSAQPT7aV_W2GWcinnkQU'
|
||||||
|
TELEGRAM_CHAT_ID = '394151541'
|
||||||
|
|
||||||
|
# Конфигурация Email
|
||||||
|
# EMAIL = 'seligenenko.a@maverik.ru'
|
||||||
|
# PASSWORD = '9Jufd22Mwvzp07dvpELn'
|
||||||
|
EMAIL = 'info@maverik.ru'
|
||||||
|
PASSWORD = 'HWPwY9Ty7QHiusubV31x'
|
||||||
|
IMAP_SERVER = 'mail.maverik.ru'
|
||||||
|
SMTP_SERVER = 'mail.maverik.ru'
|
||||||
|
FOLDER = 'INBOX/0СУБСИДИИ'
|
||||||
|
SAVE_DIRECTORY = Path(r"/opt/autorun/auto_tkp/files")
|
||||||
|
# SAVE_DIRECTORY = Path(r"\\192.168.0.13\obmen\ТКПСубсидии")
|
||||||
|
|
||||||
|
# Web Конфигурация
|
||||||
|
WEB_URL = 'https://www.tch.ru/ru-ru/Pages/Home.aspx'
|
||||||
|
WEB_USERNAME = '12ROV_kornienko'
|
||||||
|
WEB_PASSWORD = 'Rita20152015'
|
||||||
|
WEB_PASSWORD = 'Tkmaverik2025*1'
|
||||||
|
|
||||||
|
yesterday = date.today() - timedelta(days=2)
|
||||||
|
# yesterday = date.today()
|
||||||
|
|
||||||
|
async def send_telegram_notification(message):
|
||||||
|
try:
|
||||||
|
bot = Bot(token=TELEGRAM_BOT_TOKEN)
|
||||||
|
await bot.send_message(chat_id=TELEGRAM_CHAT_ID, text=message)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Субсидии:\nНе удалось отправить уведомление Telegram: {str(e)}")
|
||||||
|
|
||||||
|
def handle_error(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
error_message = f"Субсидии:\nОшибка в {func.__name__}: {str(e)}\n{traceback.format_exc()}"
|
||||||
|
logging.error(error_message)
|
||||||
|
asyncio.run(send_telegram_notification(error_message))
|
||||||
|
raise
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
def connect_to_email():
|
||||||
|
server = IMAPClient(IMAP_SERVER, use_uid=True, ssl=True)
|
||||||
|
server.login(EMAIL, PASSWORD)
|
||||||
|
return server
|
||||||
|
|
||||||
|
def extract_number_from_subject(subject):
|
||||||
|
# Извлеките цифры из темы, например «субсидия 555 612086» или «субсидия555612086»
|
||||||
|
numbers = re.findall(r'\d+', subject)
|
||||||
|
if numbers and len(''.join(numbers)) > 9:
|
||||||
|
# return ' '.join(numbers)
|
||||||
|
# if len(''.join(numbers)) > 9:
|
||||||
|
print(''.join(numbers)[-10:])
|
||||||
|
return ''.join(numbers)[-10:]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def decode_email_header(header):
|
||||||
|
"""Декодировать заголовок электронной почты для обработки различных кодировок"""
|
||||||
|
decoded_header = decode_header(header)
|
||||||
|
result = []
|
||||||
|
for part, encoding in decoded_header:
|
||||||
|
if isinstance(part, bytes):
|
||||||
|
if encoding:
|
||||||
|
result.append(part.decode(encoding))
|
||||||
|
else:
|
||||||
|
result.append(part.decode('utf-8', errors='replace'))
|
||||||
|
else:
|
||||||
|
result.append(part)
|
||||||
|
return ''.join(result)
|
||||||
|
|
||||||
|
@handle_error
|
||||||
|
def process_emails():
|
||||||
|
server = connect_to_email()
|
||||||
|
server.select_folder(FOLDER)
|
||||||
|
|
||||||
|
# Поиск непрочитанных сообщений
|
||||||
|
# messages = server.search(['UNSEEN'])
|
||||||
|
# Критерии поиска: непрочитанные сообщений перед текущей датой
|
||||||
|
# messages = server.search(['UNSEEN', 'BEFORE', yesterday.strftime('%d-%b-%Y')])
|
||||||
|
# Критерии поиска: письма, полученные вчера. Ищем письма конкретно за вчерашний день
|
||||||
|
messages = server.search(['ON', yesterday.strftime('%d-%b-%Y')])
|
||||||
|
processed_numbers = []
|
||||||
|
missing_files = []
|
||||||
|
error_extract_number = 0
|
||||||
|
multiple_attachments = [] # Список для хранения тем писем с несколькими вложениями
|
||||||
|
|
||||||
|
for uid, message_data in server.fetch(messages, ['RFC822']).items():
|
||||||
|
time.sleep(3)
|
||||||
|
email_message = email.message_from_bytes(message_data[b'RFC822'])
|
||||||
|
|
||||||
|
# Расшифровать тему
|
||||||
|
subject = decode_email_header(email_message['subject'])
|
||||||
|
number = extract_number_from_subject(subject)
|
||||||
|
|
||||||
|
if not number:
|
||||||
|
# Отметить сообщение как непрочитанное, если в теме не найдено число
|
||||||
|
server.remove_flags(uid, ['\\Seen'])
|
||||||
|
error_extract_number += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Количество вложений
|
||||||
|
attachment_count = 0
|
||||||
|
for part in email_message.walk():
|
||||||
|
if part.get_content_maintype() == 'multipart':
|
||||||
|
continue
|
||||||
|
if part.get('Content-Disposition') is None:
|
||||||
|
continue
|
||||||
|
attachment_count += 1
|
||||||
|
|
||||||
|
# Пропустить, если вложений несколько
|
||||||
|
if attachment_count > 1:
|
||||||
|
multiple_attachments.append(subject)
|
||||||
|
server.remove_flags(uid, ['\\Seen']) # Отметить как непрочитанное
|
||||||
|
continue
|
||||||
|
|
||||||
|
has_attachment = False
|
||||||
|
for part in email_message.walk():
|
||||||
|
if part.get_content_maintype() == 'multipart':
|
||||||
|
continue
|
||||||
|
if part.get('Content-Disposition') is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
filename = part.get_filename()
|
||||||
|
# Расшифровать имя файла, если оно закодировано
|
||||||
|
filename = decode_email_header(filename)
|
||||||
|
suffix = Path(filename).suffix if filename else ''
|
||||||
|
print('Имя файла:', filename)
|
||||||
|
if filename:
|
||||||
|
# Сохранение файл с номером из темы
|
||||||
|
new_filename = number + suffix
|
||||||
|
filepath = SAVE_DIRECTORY / new_filename
|
||||||
|
with open(filepath, 'wb') as f:
|
||||||
|
f.write(part.get_payload(decode=True))
|
||||||
|
# processed_numbers.append(number)
|
||||||
|
processed_numbers.append(new_filename)
|
||||||
|
has_attachment = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not has_attachment:
|
||||||
|
missing_files.append(number)
|
||||||
|
|
||||||
|
server.logout()
|
||||||
|
return processed_numbers, missing_files, error_extract_number, multiple_attachments
|
||||||
|
|
||||||
|
@handle_error
|
||||||
|
def upload_files_to_web(processed_numbers):
|
||||||
|
if not processed_numbers:
|
||||||
|
return 0, 0
|
||||||
|
|
||||||
|
disabled_passenger = [] # Пассажир инвалид
|
||||||
|
|
||||||
|
driver = webdriver.Chrome() # Убедитесь, что ChromeDriver установлен.
|
||||||
|
driver.get(WEB_URL)
|
||||||
|
driver.set_window_size(1920, 1080)
|
||||||
|
|
||||||
|
# Авторизоваться
|
||||||
|
username_field = WebDriverWait(driver, 10).until(
|
||||||
|
EC.presence_of_element_located((By.NAME, "ctl00$PlaceHolderMain$lf_Login$tb_Login"))
|
||||||
|
)
|
||||||
|
password_field = WebDriverWait(driver, 10).until(
|
||||||
|
EC.presence_of_element_located((By.NAME, "ctl00$PlaceHolderMain$lf_Login$lb_Pass"))
|
||||||
|
)
|
||||||
|
|
||||||
|
username_field.send_keys(WEB_USERNAME)
|
||||||
|
password_field.send_keys(WEB_PASSWORD)
|
||||||
|
driver.find_element(By.XPATH, "//input[@type='submit']").click()
|
||||||
|
|
||||||
|
# Перейдите к "Подтверждение документов"
|
||||||
|
next_button = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.CLASS_NAME, "a-login"))
|
||||||
|
)
|
||||||
|
next_button.click()
|
||||||
|
|
||||||
|
next_button = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.LINK_TEXT, "Управление сетью продажи, контроль и анализ данных, обмен электронными документами"))
|
||||||
|
)
|
||||||
|
next_button.click()
|
||||||
|
|
||||||
|
next_button = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.LINK_TEXT, "Стандартный вход по протоколу HTTPS"))
|
||||||
|
)
|
||||||
|
next_button.click()
|
||||||
|
|
||||||
|
next_button = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.LINK_TEXT, "Организация продаж"))
|
||||||
|
)
|
||||||
|
next_button.click()
|
||||||
|
|
||||||
|
next_button = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.LINK_TEXT, "Подтверждающие документы"))
|
||||||
|
)
|
||||||
|
next_button.click()
|
||||||
|
|
||||||
|
successful_uploads = []
|
||||||
|
failed_uploads = []
|
||||||
|
|
||||||
|
for number in processed_numbers:
|
||||||
|
print('Обрабатывается: ', Path(number).stem)
|
||||||
|
try:
|
||||||
|
time.sleep(10)
|
||||||
|
dropdown = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.TAG_NAME, "select"))
|
||||||
|
)
|
||||||
|
select = Select(dropdown)
|
||||||
|
select.select_by_visible_text("Отправлено в АВС")
|
||||||
|
|
||||||
|
# Ввод номера
|
||||||
|
number_field = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.CSS_SELECTOR, "input[data-bind='value: filter.ticketNo']"))
|
||||||
|
)
|
||||||
|
number_field.clear()
|
||||||
|
time.sleep(1)
|
||||||
|
number_field.send_keys(Path(number).stem)
|
||||||
|
|
||||||
|
current_date_obj = datetime.now().date()
|
||||||
|
new_date_obj = current_date_obj - timedelta(days=9)
|
||||||
|
new_date_str = new_date_obj.strftime("%d.%m.%Y")
|
||||||
|
input_field = driver.find_element(By.CSS_SELECTOR, "input[data-bind='date: filter.modifyDateFrom']")
|
||||||
|
input_field.clear()
|
||||||
|
time.sleep(0.5)
|
||||||
|
input_field.click()
|
||||||
|
driver.execute_script("arguments[0].value = arguments[1];", input_field, new_date_str)
|
||||||
|
# input_field.send_keys(new_date_str)
|
||||||
|
|
||||||
|
time.sleep(4)
|
||||||
|
form_button = driver.find_element(By.XPATH, "//button[contains(text(), 'Сформировать')]")
|
||||||
|
# actions = ActionChains(driver)
|
||||||
|
# actions.move_to_element(form_button).perform() # Перемещение курсора к элементу
|
||||||
|
# actions.click().perform() # Клик
|
||||||
|
form_button.click()
|
||||||
|
# driver.execute_script("arguments[0].click();", form_button)
|
||||||
|
print('Сформировать')
|
||||||
|
|
||||||
|
time.sleep(10)
|
||||||
|
edit_link = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.CSS_SELECTOR, "a.i-edit"))
|
||||||
|
)
|
||||||
|
edit_link.click()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Скрытый родительский эелемент
|
||||||
|
# driver.find_element(By.XPATH, "//td[contains(text(),'Справка об инвалидности')]")
|
||||||
|
WebDriverWait(driver, 10).until(
|
||||||
|
EC.presence_of_element_located((By.XPATH, "//td[contains(text(),'Справка об инвалидности')]/parent::tr[contains(@style, 'display: none')]"))
|
||||||
|
)
|
||||||
|
# print('Инвалид: ', edit_link.text, edit_link.is_displayed())
|
||||||
|
# print('Инвалид: ', disabled.text, disabled.is_displayed(), number)
|
||||||
|
# driver.back()
|
||||||
|
# time.sleep(5)
|
||||||
|
# continue
|
||||||
|
except:
|
||||||
|
# print('Закрываю')
|
||||||
|
disabled_passenger.append(number)
|
||||||
|
# time.sleep(5)
|
||||||
|
driver.back()
|
||||||
|
continue
|
||||||
|
# pass
|
||||||
|
|
||||||
|
# Загрузить файл
|
||||||
|
upload_button = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.CSS_SELECTOR, "button[data-bind='visible: model.addButtonVisible, click: attach']"))
|
||||||
|
)
|
||||||
|
upload_button.click()
|
||||||
|
time.sleep(3)
|
||||||
|
# file_path = os.path.join(SAVE_DIRECTORY, f"{number}.pdf")
|
||||||
|
# file_input.send_keys(file_path)
|
||||||
|
# pyautogui.typewrite(os.path.join(SAVE_DIRECTORY, f"{number}.pdf"))
|
||||||
|
pyautogui.typewrite(str(SAVE_DIRECTORY / number))
|
||||||
|
time.sleep(1)
|
||||||
|
pyautogui.press('enter')
|
||||||
|
|
||||||
|
time.sleep(7)
|
||||||
|
# Проверяется, подгружен ли файл
|
||||||
|
try:
|
||||||
|
present_element = WebDriverWait(driver, 10).until(
|
||||||
|
EC.visibility_of_element_located((By.CSS_SELECTOR, "a[data-bind='text: fileName, attr: { href: \\'/Attachments/Get?fileId=\\' + attachmentId() }']"))
|
||||||
|
)
|
||||||
|
print(present_element)
|
||||||
|
except:
|
||||||
|
error_message = f"Ссылка на файл отсутствует."
|
||||||
|
logging.error(error_message)
|
||||||
|
pyautogui.press('esc')
|
||||||
|
pyautogui.press('esc')
|
||||||
|
failed_uploads.append(number)
|
||||||
|
driver.back()
|
||||||
|
continue
|
||||||
|
|
||||||
|
send_button = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.CSS_SELECTOR, "button[data-bind='click: $parent.action, text: actionName, visible: actionName() != null']"))
|
||||||
|
)
|
||||||
|
send_button.click()
|
||||||
|
|
||||||
|
modal_button = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.CSS_SELECTOR, "button[modal='true']"))
|
||||||
|
)
|
||||||
|
modal_button.click()
|
||||||
|
|
||||||
|
successful_uploads.append(number)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Не удалось загрузить файл для номера {number}: {str(e)}")
|
||||||
|
failed_uploads.append(number)
|
||||||
|
|
||||||
|
driver.quit()
|
||||||
|
return successful_uploads, failed_uploads, disabled_passenger
|
||||||
|
|
||||||
|
@handle_error
|
||||||
|
def send_report_email(successful_uploads, failed_uploads, missing_files, error_extract_number, multiple_attachments, disabled_passenger):
|
||||||
|
msg = MIMEMultipart()
|
||||||
|
msg['From'] = EMAIL
|
||||||
|
# msg['To'] = EMAIL
|
||||||
|
msg['To'] = '109@maverik.ru'
|
||||||
|
msg['Subject'] = 'Отчет по обработке субсидий'
|
||||||
|
|
||||||
|
body = f"""
|
||||||
|
Отчет по обработке субсидий:
|
||||||
|
|
||||||
|
Успешно загружены файлы для номеров:
|
||||||
|
{', '.join(successful_uploads) if successful_uploads else 'Нет'}
|
||||||
|
|
||||||
|
Не удалось загрузить файлы для номеров:
|
||||||
|
{', '.join(failed_uploads) if failed_uploads else 'Нет'}
|
||||||
|
|
||||||
|
Отсутствуют файлы для номеров:
|
||||||
|
{', '.join(missing_files) if missing_files else 'Нет'}
|
||||||
|
|
||||||
|
Количество писем, в теме которых нет билета:
|
||||||
|
{error_extract_number if error_extract_number else 'Нет'}
|
||||||
|
|
||||||
|
Пропущены письма с несколькими вложениями:
|
||||||
|
{', '.join(multiple_attachments) if multiple_attachments else 'Нет'}
|
||||||
|
|
||||||
|
В билетах пассажир с инвалидностью:
|
||||||
|
{', '.join(disabled_passenger) if disabled_passenger else 'Нет'}
|
||||||
|
"""
|
||||||
|
|
||||||
|
msg.attach(MIMEText(body, 'plain'))
|
||||||
|
|
||||||
|
server = smtplib.SMTP(SMTP_SERVER)
|
||||||
|
server.starttls()
|
||||||
|
server.login(EMAIL, PASSWORD)
|
||||||
|
server.send_message(msg)
|
||||||
|
server.quit()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Создайте папку для сохранения, если она не существует
|
||||||
|
SAVE_DIRECTORY.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Обработка электронных писем
|
||||||
|
processed_numbers, missing_files, error_extract_number, multiple_attachments = process_emails()
|
||||||
|
print(processed_numbers)
|
||||||
|
|
||||||
|
# Загрузить файлы на ресурс
|
||||||
|
successful_uploads, failed_uploads, disabled_passenger = upload_files_to_web(processed_numbers)
|
||||||
|
|
||||||
|
# Отправить отчет по электронной почте
|
||||||
|
send_report_email(successful_uploads, failed_uploads, missing_files, error_extract_number, multiple_attachments, disabled_passenger)
|
||||||
|
|
||||||
|
logging.info("Субсидии:\nСкрипт успешно завершен")
|
||||||
|
asyncio.run(send_telegram_notification("Субсидии:\nСкрипт успешно завершен"))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_message = f"Субсидии:\nСкрипт не выполнен из-за ошибки: {str(e)}\n{traceback.format_exc()}"
|
||||||
|
logging.error(error_message)
|
||||||
|
asyncio.run(send_telegram_notification(error_message))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user