Добавить chek_shifr_mail
This commit is contained in:
commit
115fa7263a
402
chek_shifr_mail
Normal file
402
chek_shifr_mail
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton, QMessageBox, QFileDialog, QInputDialog
|
||||||
|
from PyQt5.QtCore import QThread, pyqtSignal
|
||||||
|
from exchangelib import Q, DELEGATE, Account, Credentials, Configuration, HTMLBody, EWSTimeZone, Folder
|
||||||
|
from mattermostdriver import Driver
|
||||||
|
from html2text import HTML2Text
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
from PyQt5.QtGui import QClipboard
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
# Установка московского часового пояса
|
||||||
|
EWS_MOSCOW_TZ = EWSTimeZone('Europe/Moscow')
|
||||||
|
CREDENTIALS_FILE = 'credentials.json'
|
||||||
|
|
||||||
|
class ScriptThread(QThread):
|
||||||
|
finished = pyqtSignal()
|
||||||
|
error = pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, interval, key):
|
||||||
|
super().__init__()
|
||||||
|
self.interval = interval
|
||||||
|
self.key = key
|
||||||
|
self.running = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while self.running:
|
||||||
|
try:
|
||||||
|
check_outlook_messages(self.key)
|
||||||
|
except Exception as e:
|
||||||
|
self.error.emit(str(e))
|
||||||
|
time.sleep(self.interval)
|
||||||
|
|
||||||
|
logging.info("Script thread has been stopped.")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
class LoginWindow(QWidget):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.key = None
|
||||||
|
self.script_thread = None
|
||||||
|
|
||||||
|
if not os.path.exists(CREDENTIALS_FILE):
|
||||||
|
self.generate_and_request_key()
|
||||||
|
else:
|
||||||
|
self.request_key()
|
||||||
|
|
||||||
|
def generate_and_request_key(self):
|
||||||
|
self.key = Fernet.generate_key()
|
||||||
|
key_str = self.key.decode()
|
||||||
|
|
||||||
|
msg_box = QMessageBox()
|
||||||
|
msg_box.setWindowTitle('Новый ключ')
|
||||||
|
msg_box.setText(f'Ваш ключ шифрования: {key_str}\n\nСохраните его в безопасном месте!')
|
||||||
|
|
||||||
|
copy_button = QPushButton('Копировать ключ')
|
||||||
|
copy_button.clicked.connect(lambda: QApplication.clipboard().setText(key_str))
|
||||||
|
|
||||||
|
msg_box.layout().addWidget(copy_button)
|
||||||
|
msg_box.exec_()
|
||||||
|
|
||||||
|
self.request_credentials()
|
||||||
|
|
||||||
|
def request_key(self):
|
||||||
|
key, ok = QInputDialog.getText(self, 'Ввод ключа', 'Введите ваш ключ шифрования:')
|
||||||
|
if ok:
|
||||||
|
try:
|
||||||
|
self.key = key.encode()
|
||||||
|
self.initUI()
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.warning(self, 'Ошибка', 'Неверный ключ. Попробуйте снова.')
|
||||||
|
self.request_key()
|
||||||
|
else:
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
def request_credentials(self):
|
||||||
|
self.initUI()
|
||||||
|
|
||||||
|
def initUI(self):
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
self.fields = {
|
||||||
|
'username': 'Username:',
|
||||||
|
'password': 'Password:',
|
||||||
|
'domain': 'Domain:',
|
||||||
|
'email': 'Email:',
|
||||||
|
'mattermost_token': 'Mattermost Token:',
|
||||||
|
'mattermost_url': 'Mattermost URL:',
|
||||||
|
'mattermost_channel_id': 'Mattermost Channel ID:',
|
||||||
|
'user_agent': 'User Agent:',
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inputs = {}
|
||||||
|
for key, label in self.fields.items():
|
||||||
|
self.inputs[key] = QLineEdit()
|
||||||
|
layout.addWidget(QLabel(label))
|
||||||
|
layout.addWidget(self.inputs[key])
|
||||||
|
|
||||||
|
self.inputs['password'].setEchoMode(QLineEdit.Password)
|
||||||
|
self.inputs['mattermost_token'].setEchoMode(QLineEdit.Password)
|
||||||
|
|
||||||
|
self.start_button = QPushButton('Start')
|
||||||
|
self.start_button.clicked.connect(self.start_script)
|
||||||
|
layout.addWidget(self.start_button)
|
||||||
|
|
||||||
|
self.stop_button = QPushButton('Stop')
|
||||||
|
self.stop_button.setEnabled(False)
|
||||||
|
self.stop_button.clicked.connect(self.stop_script)
|
||||||
|
layout.addWidget(self.stop_button)
|
||||||
|
|
||||||
|
self.setLayout(layout)
|
||||||
|
self.setWindowTitle('Login')
|
||||||
|
self.setGeometry(300, 300, 300, 500)
|
||||||
|
|
||||||
|
self.load_existing_credentials()
|
||||||
|
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
def encrypt_data(self, data):
|
||||||
|
f = Fernet(self.key)
|
||||||
|
return f.encrypt(data.encode()).decode()
|
||||||
|
|
||||||
|
def decrypt_data(self, data):
|
||||||
|
f = Fernet(self.key)
|
||||||
|
return f.decrypt(data.encode()).decode()
|
||||||
|
|
||||||
|
def load_existing_credentials(self):
|
||||||
|
if os.path.exists(CREDENTIALS_FILE):
|
||||||
|
try:
|
||||||
|
with open(CREDENTIALS_FILE, 'r') as f:
|
||||||
|
encrypted_data = json.load(f)
|
||||||
|
decrypted_data = {k: self.decrypt_data(v) for k, v in encrypted_data.items()}
|
||||||
|
for key, value in decrypted_data.items():
|
||||||
|
if key in self.inputs:
|
||||||
|
self.inputs[key].setText(value)
|
||||||
|
logging.info("Existing credentials loaded successfully.")
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.warning(self, 'Warning', f'Failed to load existing credentials: {str(e)}')
|
||||||
|
|
||||||
|
def validate_inputs(self):
|
||||||
|
for key, input_field in self.inputs.items():
|
||||||
|
if not input_field.text().strip():
|
||||||
|
return False, f'{self.fields[key]} cannot be empty'
|
||||||
|
|
||||||
|
return True, ''
|
||||||
|
|
||||||
|
def start_script(self):
|
||||||
|
is_valid, error_message = self.validate_inputs()
|
||||||
|
|
||||||
|
if not is_valid:
|
||||||
|
QMessageBox.warning(self, 'Validation Error', error_message)
|
||||||
|
return
|
||||||
|
|
||||||
|
credentials = {key: self.encrypt_data(input_field.text()) for key, input_field in self.inputs.items()}
|
||||||
|
|
||||||
|
with open(CREDENTIALS_FILE, 'w') as f:
|
||||||
|
json.dump(credentials, f)
|
||||||
|
|
||||||
|
try:
|
||||||
|
logging.info("Starting the main script.")
|
||||||
|
self.script_thread = ScriptThread(interval=10, key=self.key)
|
||||||
|
|
||||||
|
self.script_thread.finished.connect(self.on_script_finished)
|
||||||
|
self.script_thread.error.connect(self.on_script_error)
|
||||||
|
|
||||||
|
self.script_thread.start()
|
||||||
|
|
||||||
|
self.start_button.setEnabled(False)
|
||||||
|
self.stop_button.setEnabled(True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, 'Error', f'Failed to start the main script: {str(e)}')
|
||||||
|
|
||||||
|
def stop_script(self):
|
||||||
|
if self.script_thread and self.script_thread.isRunning():
|
||||||
|
logging.info("Stopping the script thread.")
|
||||||
|
self.script_thread.stop()
|
||||||
|
self.start_button.setEnabled(True)
|
||||||
|
self.stop_button.setEnabled(False)
|
||||||
|
|
||||||
|
self.on_script_finished()
|
||||||
|
|
||||||
|
def on_script_finished(self):
|
||||||
|
logging.info("Script finished successfully.")
|
||||||
|
|
||||||
|
def on_script_error(self, error_message):
|
||||||
|
logging.error(f"Script error: {error_message}")
|
||||||
|
QMessageBox.critical(self, 'Script Error', error_message)
|
||||||
|
|
||||||
|
def load_credentials(filename: str, key: bytes) -> dict:
|
||||||
|
with open(filename, 'r') as file:
|
||||||
|
encrypted_data = json.load(file)
|
||||||
|
|
||||||
|
f = Fernet(key)
|
||||||
|
decrypted_data = {k: f.decrypt(v.encode()).decode() for k, v in encrypted_data.items()}
|
||||||
|
del f
|
||||||
|
return decrypted_data
|
||||||
|
|
||||||
|
class TiMeManager(object):
|
||||||
|
def __init__(self, token: str, key: bytes):
|
||||||
|
credentials_data = load_credentials(CREDENTIALS_FILE, key)
|
||||||
|
url = credentials_data['mattermost_url']
|
||||||
|
|
||||||
|
self.mmDriver = Driver(options={
|
||||||
|
'url': url,
|
||||||
|
'token': token,
|
||||||
|
'scheme': 'https',
|
||||||
|
'port': 443,
|
||||||
|
'headers': {"User-Agent": credentials_data['user_agent']},
|
||||||
|
'debug': False
|
||||||
|
})
|
||||||
|
self.mmDriver.login()
|
||||||
|
|
||||||
|
def post_message(self, channel_id: str, message: str):
|
||||||
|
post_log = self.mmDriver.posts.create_post(options={
|
||||||
|
'channel_id': channel_id,
|
||||||
|
'message': message
|
||||||
|
})
|
||||||
|
return post_log
|
||||||
|
|
||||||
|
def format_text(text: str, words_to_bold) -> str:
|
||||||
|
for word in words_to_bold:
|
||||||
|
pattern = r'\b' + re.escape(word) + r'\b'
|
||||||
|
text = re.sub(pattern, f'**{word}**\n', text, flags=re.IGNORECASE)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def extract_text_from_html(html_content: str) -> str:
|
||||||
|
h = HTML2Text()
|
||||||
|
h.ignore_links = False
|
||||||
|
h.ignore_images = True
|
||||||
|
h.ignore_emphasis = True
|
||||||
|
h.body_width = 0
|
||||||
|
|
||||||
|
text = h.handle(html_content)
|
||||||
|
lines = [line.strip() for line in text.split('\n') if line.strip()]
|
||||||
|
uniform_text = ' '.join(lines)
|
||||||
|
|
||||||
|
max_length = 4000
|
||||||
|
if len(uniform_text) > max_length:
|
||||||
|
uniform_text = uniform_text[:max_length] + "...\n(Содержимое сокращено)"
|
||||||
|
|
||||||
|
return uniform_text
|
||||||
|
|
||||||
|
def send_message_to_mattermost(message: str, key: bytes):
|
||||||
|
credentials_data = load_credentials(CREDENTIALS_FILE, key)
|
||||||
|
token = credentials_data['mattermost_token']
|
||||||
|
channel_id = credentials_data['mattermost_channel_id']
|
||||||
|
|
||||||
|
time_driver = TiMeManager(token=token, key=key)
|
||||||
|
|
||||||
|
try:
|
||||||
|
post_log = time_driver.post_message(channel_id=channel_id, message=message)
|
||||||
|
logging.info(f"Message sent to Mattermost: {post_log['message']}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to send message to Mattermost: {str(e)}")
|
||||||
|
|
||||||
|
def move_message_to_mysd(account, message):
|
||||||
|
try:
|
||||||
|
# Ищем папку "mysd" среди всех доступных папок
|
||||||
|
mysd_folder = next((folder for folder in account.root.walk() if folder.name.lower() == "mysd"), None)
|
||||||
|
|
||||||
|
if not mysd_folder:
|
||||||
|
logging.error("Папка 'mysd' не найдена")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Перемещаем сообщение в найденную папку
|
||||||
|
message.move(to_folder=mysd_folder)
|
||||||
|
logging.info(f"Сообщение '{message.subject}' успешно перемещено в папку 'mysd'")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Ошибка при перемещении сообщения в папку 'mysd': {str(e)}")
|
||||||
|
|
||||||
|
def check_outlook_messages(key: bytes):
|
||||||
|
"""Проверка непрочитанных сообщений в Outlook и их отправка в Mattermost"""
|
||||||
|
try:
|
||||||
|
credentials_data = load_credentials(CREDENTIALS_FILE, key)
|
||||||
|
|
||||||
|
username = credentials_data['username']
|
||||||
|
password = credentials_data['password']
|
||||||
|
domain = credentials_data['domain']
|
||||||
|
email = credentials_data['email']
|
||||||
|
|
||||||
|
subject_filters = [
|
||||||
|
"Вы назначены ответственным по обращению",
|
||||||
|
"В вашу ответственность поступил запрос"
|
||||||
|
]
|
||||||
|
|
||||||
|
words_to_remove = [
|
||||||
|
"Группа", "Критичность", "Низкая", "Средняя", "Высокая", "Техническая поддержка сотрудников",
|
||||||
|
"Тема", "Вы назначены ответственным по обращению",
|
||||||
|
"Отдел технической поддержки пользователей",
|
||||||
|
"Ваш ответ на это письмо добавим комментарием к обращению",
|
||||||
|
"Forge — больше, чем платформа поддержки",
|
||||||
|
"Сведения об устройстве: Приложение:", "InformerModule", "Версия:", "v4.80",
|
||||||
|
"From: support_in support_in @ tinkoff.ru Sent:",
|
||||||
|
"PM To: Dmitriy Serov d.m.serov@tbank.ru Subject: [Forge] (19519859)",
|
||||||
|
"Назначил", "SupportAgent","Software"
|
||||||
|
]
|
||||||
|
|
||||||
|
words_to_bold = ["Описание","проблемы:", "Подробное",
|
||||||
|
"Инструкция для первой линии:",
|
||||||
|
"Рабочее время Решения в категории:",
|
||||||
|
"Сведения о сотруднике:", "Руководитель:",
|
||||||
|
"Почта для нотификации:", "Тип действия с ПО:",
|
||||||
|
"Какая ошибка возникает ?:", "Дополнительное описание:",
|
||||||
|
"Напишите","InformerModule v4.81"
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
credentials = Credentials(username=f"{domain}\\{username}", password=password)
|
||||||
|
config = Configuration(server='mail.tinkoff.ru', credentials=credentials)
|
||||||
|
|
||||||
|
account = Account(primary_smtp_address=email,
|
||||||
|
config=config,
|
||||||
|
autodiscover=False,
|
||||||
|
access_type=DELEGATE)
|
||||||
|
|
||||||
|
# Добавляем новую функцию здесь
|
||||||
|
def add_at_to_logins(text: str) -> str:
|
||||||
|
text = re.sub(r'(Login: )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
text = re.sub(r'(сотрудника: )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
text = re.sub(r'(сотрудника )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
text = re.sub(r'(логин.: )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
text = re.sub(r'(Инициатор: )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
text = re.sub(r'(Логин: )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
text = re.sub(r'(доступ\?: )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
text = re.sub(r'(проблема\?: )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
text = re.sub(r'(оператора: )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
text = re.sub(r'(доступ:: )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
text = re.sub(r'(dACL: )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
text = re.sub(r'(wologin: )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
text = re.sub(r'(Outlook?: )([a-zA-Z]+\.[a-zA-Z]+)', r'\1@\2', text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def format_text(text: str, words_to_bold: list) -> str:
|
||||||
|
for word in words_to_bold:
|
||||||
|
pattern = r'\b' + re.escape(word) + r'\b'
|
||||||
|
text = re.sub(pattern, f'\n**{word}**', text, flags=re.IGNORECASE)
|
||||||
|
return text
|
||||||
|
|
||||||
|
filter_query = Q(is_read=False) & (
|
||||||
|
Q(subject__contains=subject_filters[0]) |
|
||||||
|
Q(subject__contains=subject_filters[1])
|
||||||
|
)
|
||||||
|
|
||||||
|
unread_messages = account.inbox.filter(filter_query).order_by('-datetime_received')
|
||||||
|
|
||||||
|
for item in unread_messages:
|
||||||
|
message_datetime = item.datetime_received.astimezone(EWS_MOSCOW_TZ)
|
||||||
|
|
||||||
|
if isinstance(item.body, HTMLBody):
|
||||||
|
content = extract_text_from_html(item.body)
|
||||||
|
else:
|
||||||
|
content = item.text_body if hasattr(item, 'text_body') else str(item.body)
|
||||||
|
|
||||||
|
for word in words_to_remove:
|
||||||
|
content = content.replace(word, '')
|
||||||
|
|
||||||
|
content = format_text(content, words_to_bold)
|
||||||
|
content = re.sub(r'\|', '', content)
|
||||||
|
content = re.sub(r'-+', '', content)
|
||||||
|
content = re.sub(r'\s+', ' ', content).strip()
|
||||||
|
|
||||||
|
processed_content = add_at_to_logins(content)
|
||||||
|
|
||||||
|
message_body = (
|
||||||
|
f"Новое сообщение: @all\n"
|
||||||
|
f"Тема: {item.subject}\n"
|
||||||
|
f"От: {item.sender.email_address}\n"
|
||||||
|
f"Дата: {message_datetime.strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||||
|
f"Содержимое:\n{processed_content}"
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info(f"Найдено новое сообщение: {item.subject}")
|
||||||
|
|
||||||
|
send_message_to_mattermost(message_body, key)
|
||||||
|
|
||||||
|
item.is_read = True
|
||||||
|
item.save()
|
||||||
|
logging.info(f"Сообщение отмечено как прочитанное: {item.subject}")
|
||||||
|
|
||||||
|
move_message_to_mysd(account, item)
|
||||||
|
|
||||||
|
if not unread_messages:
|
||||||
|
logging.info("Новых сообщений не найдено")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Ошибка при проверке сообщений Outlook: {str(e)}")
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
logging.error("Файл с учетными данными не найден. Требуется повторный ввод данных.")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
ex = LoginWindow()
|
||||||
|
sys.exit(app.exec_())
|
Loading…
Reference in New Issue
Block a user