Spaces:
Paused
Paused
import asyncio | |
import json | |
import os | |
import urllib.parse | |
from datetime import datetime | |
from aiogram import Bot, Dispatcher, types, F | |
from aiogram.filters import Command | |
from aiogram.utils.keyboard import ReplyKeyboardBuilder, InlineKeyboardBuilder | |
from flask import Flask, request, jsonify, render_template_string | |
import logging | |
import threading | |
from huggingface_hub import HfApi, hf_hub_download | |
from huggingface_hub.utils import RepositoryNotFoundError | |
from werkzeug.utils import secure_filename | |
# Настройка логирования | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
# Инициализация бота и Flask | |
BOT_TOKEN = '7734802681:AAGKHGG8O9uNk64JWTHH5yqXzvSxCcoLUdA' # Замените на токен вашего бота | |
bot = Bot(token=BOT_TOKEN) | |
dp = Dispatcher() | |
app = Flask(__name__) | |
# Путь для хранения данных (товары и заказы) | |
DATA_FILE = 'data.json' | |
# Настройки Hugging Face | |
REPO_ID = "flpolprojects/Clients" # Замените на ваш репозиторий | |
HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Установите переменную окружения HF_TOKEN | |
HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") # Установите переменную окружения HF_TOKEN_READ | |
# Функция для загрузки данных из Hugging Face Hub | |
def load_data(): | |
try: | |
download_db_from_hf() | |
with open(DATA_FILE, 'r', encoding='utf-8') as f: | |
loaded_data = json.load(f) | |
# Проверка структуры JSON: ожидается словарь с ключами 'products' и 'orders' | |
if not (isinstance(loaded_data, dict) and 'products' in loaded_data and 'orders' in loaded_data): | |
logger.error("Неверная структура JSON файла, ожидается словарь с ключами 'products' и 'orders'. Используем дефолтное значение.") | |
return {'products': [], 'orders': []} | |
return loaded_data | |
except FileNotFoundError: | |
logger.warning("Локальный файл базы данных не найден после скачивания.") | |
return {'products': [], 'orders': []} | |
except json.JSONDecodeError: | |
logger.error("Ошибка при загрузке данных: Невозможно декодировать JSON файл.") | |
return {'products': [], 'orders': []} | |
except RepositoryNotFoundError: | |
logger.error("Репозиторий не найден. Создание локальной базы данных.") | |
return {'products': [], 'orders': []} | |
except Exception as e: | |
logger.error(f"Ошибка при загрузке данных: {e}") | |
return {'products': [], 'orders': []} | |
# Функция для сохранения данных в Hugging Face Hub | |
def save_data(data): | |
try: | |
with open(DATA_FILE, 'w', encoding='utf-8') as f: | |
json.dump(data, f, ensure_ascii=False, indent=4) | |
upload_db_to_hf() | |
except Exception as e: | |
logger.error(f"Ошибка при сохранении данных: {e}") | |
# Функция для загрузки базы данных из Hugging Face Hub | |
def upload_db_to_hf(): | |
try: | |
api = HfApi() | |
api.upload_file( | |
path_or_fileobj=DATA_FILE, | |
path_in_repo=DATA_FILE, | |
repo_id=REPO_ID, | |
repo_type="dataset", | |
token=HF_TOKEN_WRITE, | |
commit_message=f"Автоматическое резервное копирование базы данных {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" | |
) | |
logger.info("Резервная копия JSON базы успешно загружена на Hugging Face.") | |
except Exception as e: | |
logger.error(f"Ошибка при загрузке резервной копии: {e}") | |
# Функция для скачивания базы данных из Hugging Face Hub | |
def download_db_from_hf(): | |
try: | |
hf_hub_download( | |
repo_id=REPO_ID, | |
filename=DATA_FILE, | |
repo_type="dataset", | |
token=HF_TOKEN_READ, | |
local_dir=".", | |
local_dir_use_symlinks=False | |
) | |
logger.info("JSON база успешно скачана из Hugging Face.") | |
except RepositoryNotFoundError as e: | |
logger.error(f"Репозиторий не найден: {e}") | |
raise | |
except Exception as e: | |
logger.error(f"Ошибка при скачивании JSON базы: {e}") | |
raise | |
# Загрузка данных | |
data = load_data() | |
# Клавиатуры | |
def get_main_keyboard(): | |
builder = ReplyKeyboardBuilder() | |
builder.button(text="📋 Меню") | |
builder.button(text="🛒 Корзина") | |
builder.button(text="📦 Заказы") | |
builder.adjust(2) | |
return builder.as_markup(resize_keyboard=True) | |
def get_product_keyboard(product_id): | |
builder = InlineKeyboardBuilder() | |
builder.button(text="Добавить в корзину", callback_data=f"add_{product_id}") | |
return builder.as_markup() | |
# Обработчики для бота | |
async def cmd_start(message: types.Message): | |
await message.answer("Привет! Я твой бот-магазин. Выбери действие:", reply_markup=get_main_keyboard()) | |
async def show_products(message: types.Message): | |
if not data['products']: | |
await message.answer("Нет доступных товаров.") | |
return | |
for product in data['products']: | |
photo_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{product['photo']}" if 'photo' in product else None | |
caption = f"🏷 {product['name']} - {product['price']} руб.\nОписание: {product['description']}\n/id: {product['id']}" | |
if photo_url: | |
try: | |
await bot.send_photo(chat_id=message.chat.id, photo=photo_url, caption=caption, reply_markup=get_product_keyboard(product['id'])) | |
except Exception as e: | |
logger.error(f"Ошибка при отправке фото: {e}") | |
await message.answer(caption, reply_markup=get_product_keyboard(product['id'])) | |
else: | |
await message.answer(caption, reply_markup=get_product_keyboard(product['id'])) | |
async def show_cart(message: types.Message): | |
user_id = message.from_user.id | |
cart = next((o for o in data['orders'] if o['user_id'] == user_id and not o['completed']), None) | |
if not cart or not cart['items']: | |
await message.answer("Ваша корзина пуста.") | |
return | |
total = 0 | |
response = "Ваша корзина:\n" | |
for item in cart['items']: | |
product = next(p for p in data['products'] if p['id'] == item['product_id']) | |
response += f"🏷 {product['name']} - {product['price']} руб. x {item['quantity']}\n" | |
total += product['price'] * item['quantity'] | |
response += f"\nИтого: {total} руб." | |
builder = InlineKeyboardBuilder() | |
builder.button(text="Оформить заказ", callback_data=f"complete_{user_id}") | |
await message.answer(response, reply_markup=builder.as_markup()) | |
async def add_to_cart(callback_query: types.CallbackQuery): | |
product_id = int(callback_query.data.split('_')[1]) | |
product = next((p for p in data['products'] if p['id'] == product_id), None) | |
if product: | |
user_id = callback_query.from_user.id | |
cart = next((o for o in data['orders'] if o['user_id'] == user_id and not o['completed']), None) | |
if not cart: | |
cart = {'user_id': user_id, 'items': [], 'completed': False, 'date': datetime.now().isoformat()} | |
data['orders'].append(cart) | |
cart['items'].append({'product_id': product_id, 'quantity': 1}) | |
save_data(data) | |
await bot.answer_callback_query(callback_query.id, "Товар добавлен в корзину!") | |
else: | |
await bot.answer_callback_query(callback_query.id, "Товар не найден.") | |
async def complete_order(callback_query: types.CallbackQuery): | |
user_id = int(callback_query.data.split('_')[1]) | |
cart = next((o for o in data['orders'] if o['user_id'] == user_id and not o['completed']), None) | |
if cart and cart['items']: | |
total = 0 | |
cart_text = "Привет, я хочу сделать заказ:\n" | |
for item in cart['items']: | |
product = next((p for p in data['products'] if p['id'] == item['product_id']), None) | |
if product: | |
cart_text += f"{product['name']} - {product['price']} руб. x {item['quantity']}\n" | |
total += product['price'] * item['quantity'] | |
cart_text += f"\nИтого: {total} руб." | |
encoded_text = urllib.parse.quote(cart_text) | |
whatsapp_link = f"https://wa.me/996500398754?text={encoded_text}" | |
cart['completed'] = True | |
save_data(data) | |
await bot.answer_callback_query(callback_query.id, url=whatsapp_link) | |
else: | |
await bot.answer_callback_query(callback_query.id, "Корзина пуста или заказ уже оформлен.") | |
async def show_orders(message: types.Message): | |
user_id = message.from_user.id | |
user_orders = [o for o in data['orders'] if o['user_id'] == user_id and o['completed']] | |
if not user_orders: | |
await message.answer("У вас нет оформленных заказов.") | |
return | |
for order in user_orders: | |
response = "Ваш заказ:\n" | |
total = 0 | |
for item in order['items']: | |
product = next(p for p in data['products'] if p['id'] == item['product_id']) | |
response += f"🏷 {product['name']} - {product['price']} руб. x {item['quantity']}\n" | |
total += product['price'] * item['quantity'] | |
response += f"\nИтого: {total} руб.\nДата: {order['date']}" | |
await message.answer(response) | |
# Админ-панель (Flask) с HTML, CSS и JavaScript в строке | |
admin_html = """ | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Админ-панель</title> | |
<style> | |
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f0f0f0; } | |
.container { max-width: 800px; margin: 0 auto; } | |
.product { border: 1px solid #ccc; padding: 15px; margin: 10px 0; background-color: #fff; border-radius: 5px; } | |
form { background-color: #fff; padding: 15px; border-radius: 5px; margin-bottom: 20px; } | |
input, textarea { width: 100%; margin: 5px 0; padding: 8px; } | |
button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; } | |
button:hover { background-color: #45a049; } | |
img { max-width: 100px; max-height: 100px; } | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h1>Управление товарами</h1> | |
<form id="addProductForm" method="POST" enctype="multipart/form-data" action="/add_product"> | |
<input type="text" name="name" placeholder="Название" required><br> | |
<input type="number" name="price" placeholder="Цена" step="0.01" required><br> | |
<textarea name="description" placeholder="Описание" required></textarea><br> | |
<input type="file" name="photo" accept="image/*"><br> | |
<button type="submit">Добавить товар</button> | |
</form> | |
<h2>Существующие товары</h2> | |
{% if products %} | |
{% for product in products %} | |
<div class="product"> | |
{{ product.name }} - {{ product.price }} руб.<br> | |
{{ product.description }}<br> | |
{% if product.photo %} | |
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product.photo }}" alt="{{ product.name }}"> | |
{% endif %} | |
<button onclick="deleteProduct({{ product.id }})">Удалить</button> | |
</div> | |
{% endfor %} | |
{% else %} | |
<p>Нет товаров.</p> | |
{% endif %} | |
<h2>Заказы</h2> | |
{% if orders %} | |
{% for order in orders %} | |
<div class="product"> | |
Пользователь: {{ order.user_id }}<br> | |
Дата: {{ order.date }}<br> | |
Товары: | |
{% for item in order['items'] %} | |
{% for product in products %} | |
{% if product.id == item.product_id %} | |
{{ item.quantity }} x {{ product.name }}<br> | |
{% endif %} | |
{% endfor %} | |
{% endfor %} | |
</div> | |
{% endfor %} | |
{% else %} | |
<p>Нет заказов.</p> | |
{% endif %} | |
</div> | |
<script> | |
async function deleteProduct(productId) { | |
const response = await fetch(`/delete_product/${productId}`, { method: 'POST' }); | |
if (response.ok) window.location.reload(); | |
} | |
</script> | |
</body> | |
</html> | |
""" | |
def admin_panel(): | |
try: | |
logger.info("Rendering admin panel with products and orders") | |
return render_template_string(admin_html, products=data['products'], orders=data['orders'], repo_id=REPO_ID) | |
except Exception as e: | |
logger.error(f"Ошибка в шаблоне: {e}") | |
return "Ошибка сервера. Проверь логи.", 500 | |
def add_product(): | |
try: | |
if request.method == 'POST': | |
logger.info("Adding new product") | |
name = request.form['name'] | |
price = float(request.form['price']) | |
description = request.form['description'] | |
photo = request.files['photo'] # Получаем файл фотографии | |
product_id = max((p['id'] for p in data['products']), default=0) + 1 | |
photo_filename = None | |
if photo: | |
photo_filename = secure_filename(photo.filename) | |
temp_path = os.path.join(".", photo_filename) # Сохраняем временно локально | |
photo.save(temp_path) | |
try: | |
api = HfApi() | |
api.upload_file( | |
path_or_fileobj=temp_path, | |
path_in_repo=f"photos/{photo_filename}", | |
repo_id=REPO_ID, | |
repo_type="dataset", | |
token=HF_TOKEN_WRITE, | |
commit_message=f"Добавлено фото для товара {name}" | |
) | |
logger.info(f"Фото успешно загружено: {photo_filename}") | |
except Exception as e: | |
logger.error(f"Ошибка при загрузке фото: {e}") | |
return jsonify({'status': 'error', 'message': f'Ошибка при загрузке фото: {str(e)}'}), 500 | |
finally: | |
os.remove(temp_path) # Удаляем временный файл | |
data['products'].append({ | |
'id': product_id, | |
'name': name, | |
'price': price, | |
'description': description, | |
'photo': photo_filename | |
}) | |
save_data(data) | |
return jsonify({'status': 'success'}) | |
except Exception as e: | |
logger.error(f"Ошибка при добавлении товара: {e}") | |
return jsonify({'status': 'error', 'message': str(e)}), 500 | |
def delete_product(product_id): | |
try: | |
logger.info(f"Deleting product with id={product_id}") | |
data['products'] = [p for p in data['products'] if p['id'] != product_id] | |
save_data(data) | |
return jsonify({'status': 'success'}) | |
except Exception as e: | |
logger.error(f"Ошибка при удалении товара: {e}") | |
return jsonify({'status': 'error', 'message': str(e)}), 500 | |
# Запуск бота и Flask | |
async def on_startup(_): | |
logger.info("Бот запущен!") | |
def run_flask(): | |
try: | |
logger.info("Starting Flask server on port 7860") | |
app.run(host='0.0.0.0', port=7860, debug=True, use_reloader=False) | |
except Exception as e: | |
logger.error(f"Ошибка в Flask: {e}") | |
if __name__ == '__main__': | |
# Создаём и запускаем поток для Flask | |
flask_thread = threading.Thread(target=run_flask, daemon=True) | |
flask_thread.start() | |
logger.info("Flask thread started") | |
# Запускаем бота в главном потоке | |
try: | |
asyncio.run(dp.start_polling(bot, on_startup=on_startup)) | |
except KeyboardInterrupt: | |
logger.info("Stopping bot and Flask") | |
finally: | |
flask_thread.join() # Ждём завершения потока Flask при завершении программы |