import json import time import re import os from imapclient import IMAPClient from http.server import HTTPServer, BaseHTTPRequestHandler from html.parser import HTMLParser from collections import deque from datetime import datetime print("Сервер запущен, начало работы...", flush=True) # === НАСТРОЙКИ ПОЧТЫ === IMAP_SERVER = "mail.gtn.ru" EMAIL = "M.Fedorov@gtn.ru" PASSWORD = "xkd-JZj-rRL-N2G" # Файлы STATE_FILE = "processed_uids.json" QUEUE_FILE = "leads_queue.json" # Очередь лидов MAX_QUEUE_SIZE = 50 leads_queue = deque(maxlen=MAX_QUEUE_SIZE) processed_uids = set() # ========== РАБОТА С ФАЙЛОМ ОЧЕРЕДИ ========== def save_queue_to_file(): with open(QUEUE_FILE, 'w', encoding='utf-8') as f: json.dump(list(leads_queue), f, ensure_ascii=False) print(f"💾 Очередь сохранена: {len(leads_queue)} лидов") def load_queue_from_file(): global leads_queue if os.path.exists(QUEUE_FILE): try: with open(QUEUE_FILE, 'r', encoding='utf-8') as f: saved_queue = json.load(f) leads_queue = deque(saved_queue, maxlen=MAX_QUEUE_SIZE) print(f"📂 Загружена очередь из файла: {len(leads_queue)} лидов") except Exception as e: print(f"Ошибка загрузки очереди: {e}") # ========== ЗАГРУЗКА ОБРАБОТАННЫХ UID ========== if os.path.exists(STATE_FILE): try: with open(STATE_FILE, 'r') as f: processed_uids = set(json.load(f)) print(f"📂 Загружено {len(processed_uids)} обработанных UID") except: pass def save_state(): with open(STATE_FILE, 'w') as f: json.dump(list(processed_uids), f) # ========== ПАРСИНГ HTML ========== class HTMLToTextParser(HTMLParser): def __init__(self): super().__init__() self.text = [] def handle_data(self, data): if data.strip(): self.text.append(data.strip()) def handle_starttag(self, tag, attrs): if tag in ['p', 'br', 'div', 'li', 'tr']: if self.text and self.text[-1] != '': self.text.append('') def get_text(self): return '\n'.join(self.text) def html_to_text(html): parser = HTMLToTextParser() parser.feed(html) text = parser.get_text() lines = [line.strip() for line in text.split('\n') if line.strip()] return '\n'.join(lines) def decode_subject(subject): if not subject: return "" if isinstance(subject, bytes): try: subject = subject.decode('utf-8') except: return str(subject) if subject.startswith('=?UTF-8?B?'): try: import base64 encoded = subject.split('?B?')[1].split('?=')[0] return base64.b64decode(encoded).decode('utf-8') except: return subject return subject def parse_email(subject, body_html): result = {"town": "", "phone": "", "email": "", "first_name": "", "surname": "", "second_name": "", "client_type": "", "addition": ""} text_body = html_to_text(body_html) plain_subject = decode_subject(subject) # Телефон phone_match = re.search(r"\+?7\d{10}|\+7\s\d{3}\s\d{3}\s\d{2}\s\d{2}|8\d{10}", text_body) if not phone_match: phone_match = re.search(r"Телефон[:\s]*([+\d\s]{10,20})", text_body, re.IGNORECASE) if phone_match: phone = re.sub(r'[^\d+]', '', phone_match.group(0)) if phone.startswith('8'): phone = '+7' + phone[1:] elif phone.isdigit() and len(phone) == 11: phone = '+' + phone result["phone"] = phone # Email email_match = re.search(r"Email[:\s]*([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})", text_body, re.IGNORECASE) if email_match: result["email"] = email_match.group(1) # Имя name_match = re.search(r"Имя[:\s]*(.+)", text_body, re.IGNORECASE) if name_match: result["first_name"] = name_match.group(1).strip() # Фамилия surname_match = re.search(r"Фамилия[:\s]*(.+)", text_body, re.IGNORECASE) if surname_match: result["surname"] = surname_match.group(1).strip() # Отчество second_name_match = re.search(r"Отчество[:\s]*(.+)", text_body, re.IGNORECASE) if second_name_match: result["second_name"] = second_name_match.group(1).strip() # Тип клиента client_type_match = re.search(r"Физ/Юр\. лицо[:\s]*(.+)", text_body, re.IGNORECASE) if client_type_match: result["client_type"] = client_type_match.group(1).strip() # Город towns = ["Гатчина", "Коммунар", "Сиверский", "Вырица", "Тайцы", "Дружная Горка"] for town in towns: if town.lower() in text_body.lower(): result["town"] = town break # Дата и время result["received_at"] = datetime.now().strftime("%d.%m %H:%M") # Формируем addition (исключая распарсенные данные) cleaned_body = text_body if result["first_name"]: cleaned_body = cleaned_body.replace(result["first_name"], "") if result["surname"]: cleaned_body = cleaned_body.replace(result["surname"], "") if result["second_name"]: cleaned_body = cleaned_body.replace(result["second_name"], "") if result["phone"]: cleaned_body = cleaned_body.replace(result["phone"], "") phone_clean = result["phone"].replace("+7", "").replace("+", "") if phone_clean: cleaned_body = cleaned_body.replace(phone_clean, "") if result["email"]: cleaned_body = cleaned_body.replace(result["email"], "") # Чистим лишние строки lines = [line.strip() for line in cleaned_body.split('\n') if line.strip()] cleaned_body = '\n'.join(lines) addition_lines = [f"Тема: {plain_subject}", "", cleaned_body] result["addition"] = "\n".join(addition_lines) return result def fetch_new_emails(): print("Проверяю почту...", flush=True) global processed_uids, leads_queue try: with IMAPClient(IMAP_SERVER, ssl=True) as server: server.login(EMAIL, PASSWORD) server.select_folder("Подключения") messages = server.search(["ALL"]) if not messages: return 0 ALLOWED_SENDERS = ["no_reply@gtn.ru"] new_count = 0 for msg_id in messages: if msg_id in processed_uids: continue data = server.fetch([msg_id], ["ENVELOPE", "BODY[]"]) envelope = data[msg_id][b"ENVELOPE"] sender_email = "" if envelope.from_: from_addr = envelope.from_[0] mailbox = from_addr.mailbox host = from_addr.host if isinstance(mailbox, bytes): mailbox = mailbox.decode('utf-8') if isinstance(host, bytes): host = host.decode('utf-8') if mailbox and host: sender_email = f"{mailbox}@{host}" print(f"📨 Найдено письмо от {sender_email}, UID={msg_id}") allowed = False for pattern in ALLOWED_SENDERS: if pattern.startswith('@'): if sender_email.endswith(pattern): allowed = True break else: if sender_email.lower() == pattern.lower(): allowed = True break if not allowed: print(f"⏭️ Пропущено (отправитель не в списке)") processed_uids.add(msg_id) save_state() continue body_data = data[msg_id].get(b"BODY[]", b"") subject = envelope.subject if isinstance(subject, bytes): subject = subject.decode('utf-8', errors='ignore') elif subject is None: subject = "" body = body_data.decode("utf-8", errors="ignore") body_match = re.search(r"]*>(.*?)", body, re.DOTALL | re.IGNORECASE) html_content = body_match.group(1) if body_match else body lead = parse_email(subject, html_content) leads_queue.append(lead) save_queue_to_file() processed_uids.add(msg_id) save_state() new_count += 1 print(f"✅ Взят лид от {sender_email}, телефон: {lead['phone']}") return new_count except Exception as e: print(f"Ошибка IMAP: {e}") return 0 class LeadHandler(BaseHTTPRequestHandler): def do_GET(self): global leads_queue # Обработка статических .user.js файлов if self.path.endswith(".user.js"): try: filename = self.path.split('/')[-1] filepath = f"/home/yasu/scripts/{filename}" with open(filepath, 'r', encoding='utf-8') as f: content = f.read() self.send_response(200) self.send_header("Content-Type", "application/javascript") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(content.encode('utf-8')) return except FileNotFoundError: self.send_response(404) self.end_headers() return if self.path == "/lead": lead = leads_queue.popleft() if leads_queue else None save_queue_to_file() self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(json.dumps({ "lead": lead, "queue_size": len(leads_queue) }, ensure_ascii=False).encode('utf-8')) elif self.path.startswith("/lead?index="): try: idx = int(self.path.split("=")[1]) if 0 <= idx < len(leads_queue): lead = leads_queue[idx] del leads_queue[idx] save_queue_to_file() else: lead = None self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(json.dumps({ "lead": lead, "queue_size": len(leads_queue) }, ensure_ascii=False).encode('utf-8')) except: self.send_response(400) self.end_headers() elif self.path == "/queue_list": preview_list = [] for lead in leads_queue: preview = { "first_name": lead.get("first_name", ""), "phone": lead.get("phone", ""), "email": lead.get("email", ""), "received_at": lead.get("received_at", "") } preview_list.append(preview) self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(json.dumps({ "list": preview_list, "size": len(leads_queue) }, ensure_ascii=False).encode('utf-8')) elif self.path == "/queue": self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(json.dumps({"size": len(leads_queue)}).encode()) else: self.send_response(404) self.end_headers() def do_OPTIONS(self): self.send_response(200) self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET") self.end_headers() def log_message(self, format, *args): pass def main(): # Загружаем сохранённую очередь load_queue_from_file() print("🚀 Сервер запущен на http://0.0.0.0:8765") print(f"📁 Обработанных UID: {len(processed_uids)}") print(f"📋 В очереди: {len(leads_queue)} лидов") print("Ожидание писем...") import threading http_server = HTTPServer(("0.0.0.0", 8765), LeadHandler) thread = threading.Thread(target=http_server.serve_forever, daemon=True) thread.start() while True: new = fetch_new_emails() if new > 0: print(f"📬 Забрал {new} новое(ых) письмо(а). В очереди: {len(leads_queue)}") time.sleep(10) if __name__ == "__main__": print("Запуск main()...", flush=True) main()