325 lines
12 KiB
Python
325 lines
12 KiB
Python
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
|
||
|
||
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
|
||
|
||
# Всё остальное
|
||
addition_lines = [f"Тема: {plain_subject}", "", text_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[^>]*>(.*?)</body>", 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
|
||
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", "")
|
||
}
|
||
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() |