Files
bot_vk_ikp_prodagi/form/server.py
T

306 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()