import os import json from http.server import HTTPServer, SimpleHTTPRequestHandler from datetime import datetime import sys # Automatically handle dependencies: install openpyxl if it's missing try: import openpyxl except ImportError: import subprocess print("Библиотека 'openpyxl' не найдена. Устанавливаем...") try: subprocess.check_call([sys.executable, "-m", "pip", "install", "openpyxl"]) import openpyxl print("Библиотека 'openpyxl' успешно установлена!") except Exception as e: print(f"Ошибка при автоматической установке openpyxl: {e}") print("Пожалуйста, установите её вручную: pip install openpyxl") sys.exit(1) from openpyxl import Workbook, load_workbook from openpyxl.styles import Font, Alignment, PatternFill # Add parent directory to sys.path to load database.py and config.py PARENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if PARENT_DIR not in sys.path: sys.path.append(PARENT_DIR) try: import config from database import DatabaseManager db = DatabaseManager(db_path=os.path.join(PARENT_DIR, "bot_database.db")) except Exception as e: print(f"Предупреждение: Не удалось загрузить модули базы данных/конфигурации: {e}") db = None config = None try: import vk_api from vk_api.utils import get_random_id except ImportError: vk_api = None # Path to the Excel spreadsheet in the same directory as server.py SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) FILE_NAME = os.path.join(SCRIPT_DIR, "registration_for_training.xlsx") def init_excel(): """Create the Excel file with styled headers if it doesn't exist.""" recreate = False if os.path.exists(FILE_NAME): try: wb = load_workbook(FILE_NAME) ws = wb.active # Recreate if it does not have the 'id_user' column as the first header if ws.cell(row=1, column=1).value != "id_user": recreate = True except Exception: recreate = True if recreate: try: os.remove(FILE_NAME) print("Обнаружена старая структура Excel файла. Пересоздаём с колонкой 'id_user'...") except Exception as e: print(f"Не удалось удалить старый Excel файл: {e}") if not os.path.exists(FILE_NAME): wb = Workbook() ws = wb.active ws.title = "Заявки на обучение" headers = [ "id_user", "Имя", "Номер телефона", "E-mail", "Ссылка на профиль ВКонтакте", "Согласие с офертой", "Согласие с политикой", "Согласие на рассылку", "Дата регистрации" ] ws.append(headers) # Style header cells (Bold white text, dark blue/gray background) header_font = Font(name="Calibri", size=11, bold=True, color="FFFFFF") header_fill = PatternFill(start_color="1F4E78", end_color="1F4E78", fill_type="solid") header_align = Alignment(horizontal="center", vertical="center") for col_num, header in enumerate(headers, 1): cell = ws.cell(row=1, column=col_num) cell.font = header_font cell.fill = header_fill cell.alignment = header_align # Adjust column widths column_widths = [15, 20, 25, 25, 30, 20, 22, 22, 22] for i, width in enumerate(column_widths, 1): ws.column_dimensions[openpyxl.utils.get_column_letter(i)].width = width wb.save(FILE_NAME) print(f"Создан новый файл Excel с колонкой id_user: {FILE_NAME}") def get_next_user_id(ws): """Generate the next unique user ID in the format id_001, id_002...""" max_row = ws.max_row if max_row <= 1: return "id_001" # Read the ID from the last row's first cell last_id_val = ws.cell(row=max_row, column=1).value if not last_id_val or not str(last_id_val).startswith("id_"): # Fallback to row counts if the last row ID is corrupted/not matching mask return f"id_{(max_row - 1):03d}" try: # Extract the numeric suffix, increment, and format back numeric_part = int(str(last_id_val).split("_")[1]) next_num = numeric_part + 1 return f"id_{next_num:03d}" except (IndexError, ValueError): return f"id_{max_row:03d}" def add_registration(data): """Append a new form submission row into the Excel sheet.""" init_excel() try: wb = load_workbook(FILE_NAME) ws = wb.active except Exception as e: print(f"Ошибка загрузки Excel файла, пересоздаём: {e}") init_excel() wb = load_workbook(FILE_NAME) ws = wb.active current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Auto-generate next user ID next_id = get_next_user_id(ws) row = [ next_id, data.get("name", "").strip(), data.get("phone", "").strip(), data.get("email", "").strip(), data.get("vk", "").strip(), "Да" if data.get("agreeOferta") else "Нет", "Да" if data.get("agreePolicy") else "Нет", "Да" if data.get("agreeNewsletter") else "Нет", current_time ] ws.append(row) # Align data cells new_row_index = ws.max_row data_align_left = Alignment(horizontal="left", vertical="center") data_align_center = Alignment(horizontal="center", vertical="center") for col_idx in range(1, len(row) + 1): cell = ws.cell(row=new_row_index, column=col_idx) # Left align Name (2), Email (4), VK Link (5). Center align others. if col_idx in [2, 4, 5]: cell.alignment = data_align_left else: cell.alignment = data_align_center wb.save(FILE_NAME) print(f"[{current_time}] Добавлена новая запись в Excel: ID={row[0]}, Имя={row[1]}, Телефон={row[2]}") # 1. Запись в SQLite базу данных if db: try: db.add_web_registration(next_id, data) print(f"[{current_time}] Заявка {next_id} успешно сохранена в SQLite (таблица web_registrations).") except Exception as db_err: print(f"[{current_time}] Ошибка записи заявки в SQLite: {db_err}") # 2. Отправка уведомления оператору в VK try: send_vk_operator_notification(next_id, data) except Exception as vk_err: print(f"[{current_time}] Ошибка при отправке уведомления оператору: {vk_err}") def send_vk_operator_notification(reg_id, data): """Отправить уведомление о новой веб-заявке в VK-чат оператора.""" if not vk_api or not config: print("[VK NOTIFICATION] Пропущено: vk_api или конфигурация недоступны.") return token = getattr(config, "VK_TOKEN", "your_vk_community_token_here") operator_id = getattr(config, "OPERATOR_PEER_ID", 0) if not token or token == "your_vk_community_token_here" or not operator_id: print("[VK NOTIFICATION] Пропущено: Токен VK или ID оператора не настроены в .env.") return try: vk_session = vk_api.VkApi(token=token) vk = vk_session.get_api() # Формируем сообщение для оператора message = ( f"🌐 **Новая заявка с сайта!**\n\n" f"🆔 ID заявки: {reg_id}\n" f"👤 Имя: {data.get('name', '').strip()}\n" f"📞 Телефон: {data.get('phone', '').strip()}\n" f"✉️ E-mail: {data.get('email', '').strip()}\n" f"🌐 Профиль VK: {data.get('vk', '').strip()}\n\n" f"⚖️ Согласия пользователя:\n" f"• Оферта: {'Да' if data.get('agreeOferta') else 'Нет'}\n" f"• Политика ПД: {'Да' if data.get('agreePolicy') else 'Нет'}\n" f"• Рассылка: {'Да' if data.get('agreeNewsletter') else 'Нет'}\n\n" f"Данные сохранены в Excel и базу данных SQLite." ) vk.messages.send( peer_id=operator_id, message=message, random_id=get_random_id() ) print(f"[VK NOTIFICATION] Успешно отправлено уведомление оператору (ID: {operator_id}) для заявки {reg_id}.") except Exception as e: print(f"[VK NOTIFICATION] Ошибка при отправке уведомления в VK: {e}") class FormHandler(SimpleHTTPRequestHandler): """Custom HTTP handler serving form files and processing POST requests.""" def translate_path(self, path): # Override translate_path to serve static files from the 'form' directory # even if launched from the parent directory path = SimpleHTTPRequestHandler.translate_path(self, path) rel_path = os.path.relpath(path, os.getcwd()) return os.path.join(SCRIPT_DIR, rel_path) def do_POST(self): if self.path == '/submit': content_length = int(self.headers['Content-Length']) post_data = self.rfile.read(content_length) try: data = json.loads(post_data.decode('utf-8')) add_registration(data) self.send_response(200) self.send_header('Content-Type', 'application/json') self.send_header('Access-Control-Allow-Origin', '*') # Prevent CORS errors self.end_headers() response = {"status": "success", "message": "Заявка успешно записана!"} self.wfile.write(json.dumps(response).encode('utf-8')) except PermissionError: self.send_response(500) self.send_header('Content-Type', 'application/json') self.send_header('Access-Control-Allow-Origin', '*') self.end_headers() response = { "status": "error", "message": "Таблица Excel заблокирована. Пожалуйста, закройте файл 'registration_for_training.xlsx' в Excel и отправьте форму заново." } self.wfile.write(json.dumps(response).encode('utf-8')) except Exception as e: self.send_response(500) self.send_header('Content-Type', 'application/json') self.send_header('Access-Control-Allow-Origin', '*') self.end_headers() response = {"status": "error", "message": str(e)} self.wfile.write(json.dumps(response).encode('utf-8')) else: self.send_response(404) self.end_headers() def do_OPTIONS(self): # Respond to CORS preflight requests self.send_response(204) self.send_header('Access-Control-Allow-Origin', '*') self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS') self.send_header('Access-Control-Allow-Headers', 'Content-Type') self.end_headers() def run_server(port=8000): init_excel() # Pre-create Excel on startup server_address = ('', port) httpd = HTTPServer(server_address, FormHandler) print(f"\n=======================================================") print(f"Сервер ИКП запущен на http://localhost:{port}/") print(f"Вы можете открыть форму по адресу: http://localhost:{port}/index.html") print(f"Данные будут записываться в: {FILE_NAME}") print(f"Нажмите Ctrl+C для остановки сервера.") print(f"=======================================================\n") try: httpd.serve_forever() except KeyboardInterrupt: print("\nСервер останавливается...") httpd.server_close() if __name__ == '__main__': run_server()