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, redirect 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 = '7595736142:AAHSU3WGItBkebIgjO293J2WjX5qWAne8Y8' bot = Bot(token=BOT_TOKEN) dp = Dispatcher() app = Flask(__name__) # Путь для хранения данных DATA_FILE = 'data2.json' # Настройки Hugging Face REPO_ID = "flpolprojects/Clients" HF_TOKEN_WRITE = os.getenv("HF_TOKEN") HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") # Функции для работы с данными def load_data(): try: download_db_from_hf() with open(DATA_FILE, 'r', encoding='utf-8') as f: loaded_data = json.load(f) if not (isinstance(loaded_data, dict) and 'products' in loaded_data and 'orders' in loaded_data): logger.error("Неверная структура JSON файла") loaded_data = {'products': [], 'orders': []} if "categories" not in loaded_data: loaded_data["categories"] = [] return loaded_data except Exception as e: logger.error(f"Ошибка при загрузке данных: {e}") return {'products': [], 'orders': [], 'categories': []} 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}") 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("База загружена на Hugging Face") except Exception as e: logger.error(f"Ошибка при загрузке резервной копии: {e}") 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("База скачана из Hugging Face") except Exception as e: logger.error(f"Ошибка при скачивании: {e}") raise # Периодическое копирование каждые 30 секунд def start_periodic_backup(): def backup_loop(): upload_db_to_hf() # Запускаем следующий вызов через 30 секунд threading.Timer(30, backup_loop).start() # Запускаем первый вызов threading.Timer(30, backup_loop).start() logger.info("Периодическое копирование каждые 30 секунд запущено") # Загрузка данных 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_category_keyboard(): builder = InlineKeyboardBuilder() for category in data['categories']: builder.button(text=category['name'], callback_data=f"cat_{category['id']}") builder.adjust(2) return builder.as_markup() def get_product_keyboard(product_id): builder = InlineKeyboardBuilder() builder.button(text="Добавить в корзину", callback_data=f"add_{product_id}") return builder.as_markup() # Обработчики бота @dp.message(Command("start")) async def cmd_start(message: types.Message): await message.answer("Здравствуйте ! это магазин Routine!. Выберите действие:", reply_markup=get_main_keyboard()) # Изменено условие на "📋 Каталог", чтобы соответствовать кнопке @dp.message(F.text == "📋 Каталог") async def show_categories(message: types.Message): if not data['categories']: await message.answer("Нет доступных категорий.") return await message.answer("Выберите категорию:", reply_markup=get_category_keyboard()) @dp.callback_query(F.data.startswith("cat_")) async def show_products_in_category(callback_query: types.CallbackQuery): try: cat_id = int(callback_query.data.split('_')[1]) products_in_cat = [p for p in data['products'] if p.get('category_id') == cat_id] if not products_in_cat: await bot.send_message(callback_query.from_user.id, "В этой категории нет товаров.") await bot.answer_callback_query(callback_query.id) return async def send_product_batch(products_batch): for product in products_batch: photo_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{product['photo']}" if product.get('photo') else None caption = f"🏷 {product['name']} - {product['price']} сом\nОписание: {product['description']}\n/id: {product['id']}" try: if photo_url: await bot.send_photo(chat_id=callback_query.from_user.id, photo=photo_url, caption=caption, reply_markup=get_product_keyboard(product['id'])) else: await bot.send_message(callback_query.from_user.id, caption, reply_markup=get_product_keyboard(product['id'])) except Exception as e: logger.error(f"Ошибка при отправке: {e}") await bot.send_message(callback_query.from_user.id, caption, reply_markup=get_product_keyboard(product['id'])) batch_size = 5 for i in range(0, len(products_in_cat), batch_size): batch = products_in_cat[i:i + batch_size] await send_product_batch(batch) await asyncio.sleep(0.1) await bot.answer_callback_query(callback_query.id) except Exception as e: logger.error(f"Ошибка в show_products_in_category: {e}") await bot.answer_callback_query(callback_query.id, "Ошибка при загрузке товаров") @dp.message(F.text == "🛒 Корзина") 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.get('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()) @dp.callback_query(F.data.startswith("add_")) async def add_to_cart(callback_query: types.CallbackQuery): try: 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.get('completed')), None) if not cart: cart = {'user_id': user_id, 'items': [], '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, "Товар добавлен в корзину!") except Exception as e: logger.error(f"Ошибка при добавлении в корзину: {e}") await bot.answer_callback_query(callback_query.id, "Ошибка") @dp.callback_query(F.data.startswith("complete_")) async def complete_order(callback_query: types.CallbackQuery): try: 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.get('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/996709513331?text={encoded_text}" data['orders'].remove(cart) save_data(data) await bot.send_message(user_id, f"Оформите заказ через WhatsApp:\n{whatsapp_link}") await bot.answer_callback_query(callback_query.id) except Exception as e: logger.error(f"Ошибка при оформлении заказа: {e}") await bot.answer_callback_query(callback_query.id, "Ошибка") @dp.message(F.text == "📦 Заказы") async def show_orders(message: types.Message): user_id = message.from_user.id user_orders = [o for o in data['orders'] if o.get('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) # Админ-панель admin_html = """ <!DOCTYPE html> <html> <head> <title>Админ-панель</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { font-family: Arial, sans-serif; margin: 10px; background-color: #f0f0f0; } .container { max-width: 1000px; margin: 0 auto; } .section { background-color: #fff; padding: 10px; margin-bottom: 15px; border-radius: 5px; } h1, h2, h3 { margin: 10px 0; } input, textarea, select { width: 100%; margin: 5px 0; padding: 8px; box-sizing: border-box; } button { background-color: #4CAF50; color: white; padding: 8px 12px; border: none; border-radius: 5px; cursor: pointer; width: 100%; margin: 5px 0; } button:hover { background-color: #45a049; } img { max-width: 100%; height: auto; max-height: 150px; } .item { border: 1px solid #ccc; padding: 10px; margin: 5px 0; word-wrap: break-word; } @media (max-width: 600px) { .section { padding: 8px; } button { padding: 6px 10px; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; } h3 { font-size: 1em; } } </style> </head> <body> <div class="container"> <h1>Админ-панель</h1> <div class="section"> <h2>Управление категориями</h2> <form id="addCategoryForm" method="POST" action="/add_category"> <input type="text" name="name" placeholder="Название категории" required> <button type="submit">Добавить категорию</button> </form> <h3>Существующие категории</h3> {% if categories %} {% for category in categories %} <div class="item"> {{ category.name }} (ID: {{ category.id }}) <button onclick="deleteCategory({{ category.id }})">Удалить</button> </div> {% endfor %} {% else %} <p>Нет категорий.</p> {% endif %} </div> <div class="section"> <h2>Управление товарами</h2> <form id="addProductForm" method="POST" enctype="multipart/form-data" action="/add_product"> <input type="text" name="name" placeholder="Название" required> <input type="number" name="price" placeholder="Цена" step="0.01" required> <textarea name="description" placeholder="Описание" required></textarea> <label>Категория:</label> <select name="category_id" required> <option value="">Выберите категорию</option> {% for category in categories %} <option value="{{ category.id }}">{{ category.name }}</option> {% endfor %} </select> <input type="file" name="photo" accept="image/*"> <button type="submit">Добавить товар</button> </form> <h3>Существующие товары</h3> {% if products %} {% for product in products %} <div class="item"> {{ 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 %} </div> <div class="section"> <h2>Заказы</h2> {% if orders %} {% for order in orders %} <div class="item"> Пользователь: {{ 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> </div> <script> const eventSource = new EventSource('/updates'); eventSource.onmessage = function(event) { if (event.data === 'update') { window.location.reload(); } }; eventSource.onerror = function() { console.log("Ошибка SSE, reconnecting..."); }; async function deleteProduct(productId) { const response = await fetch(`/delete_product/${productId}`, { method: 'POST' }); if (response.ok) broadcastUpdate(); } async function deleteCategory(categoryId) { const response = await fetch(`/delete_category/${categoryId}`, { method: 'POST' }); if (response.ok) broadcastUpdate(); } function broadcastUpdate() { fetch('/broadcast_update', { method: 'POST' }); } </script> </body> </html> """ update_event = threading.Event() @app.route('/') def admin_panel(): try: return render_template_string(admin_html, products=data['products'], orders=data['orders'], categories=data['categories'], repo_id=REPO_ID) except Exception as e: logger.error(f"Ошибка в шаблоне: {e}") return "Ошибка сервера", 500 @app.route('/add_product', methods=['POST']) def add_product(): try: name = request.form['name'] price = float(request.form['price']) description = request.form['description'] category_id = int(request.form['category_id']) photo = request.files.get('photo') product_id = max((p['id'] for p in data['products']), default=0) + 1 photo_filename = None if photo and photo.filename: photo_filename = secure_filename(photo.filename) temp_path = os.path.join(".", photo_filename) photo.save(temp_path) 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}" ) os.remove(temp_path) data['products'].append({ 'id': product_id, 'name': name, 'price': price, 'description': description, 'category_id': category_id, 'photo': photo_filename }) save_data(data) update_event.set() return redirect("/") except Exception as e: logger.error(f"Ошибка при добавлении товара: {e}") return jsonify({'status': 'error', 'message': str(e)}), 500 @app.route('/delete_product/<int:product_id>', methods=['POST']) def delete_product(product_id): try: data['products'] = [p for p in data['products'] if p['id'] != product_id] save_data(data) update_event.set() return jsonify({'status': 'success'}) except Exception as e: logger.error(f"Ошибка при удалении товара: {e}") return jsonify({'status': 'error', 'message': str(e)}), 500 @app.route('/add_category', methods=['POST']) def add_category(): try: name = request.form['name'] category_id = max((c['id'] for c in data['categories']), default=0) + 1 data['categories'].append({'id': category_id, 'name': name}) save_data(data) update_event.set() return redirect("/") except Exception as e: logger.error(f"Ошибка при добавлении категории: {e}") return jsonify({'status': 'error', 'message': str(e)}), 500 @app.route('/delete_category/<int:category_id>', methods=['POST']) def delete_category(category_id): try: data['categories'] = [c for c in data['categories'] if c['id'] != category_id] save_data(data) update_event.set() return jsonify({'status': 'success'}) except Exception as e: logger.error(f"Ошибка при удалении категории: {e}") return jsonify({'status': 'error', 'message': str(e)}), 500 @app.route('/updates') def sse_updates(): def stream(): while True: update_event.wait() yield "data: update\n\n" update_event.clear() return app.response_class(stream(), mimetype="text/event-stream") @app.route('/broadcast_update', methods=['POST']) def broadcast_update(): update_event.set() return jsonify({'status': 'success'}) # Запуск async def on_startup(_): logger.info("Бот запущен!") def run_flask(): app.run(host='0.0.0.0', port=7860, debug=True, use_reloader=False) if __name__ == '__main__': flask_thread = threading.Thread(target=run_flask, daemon=True) flask_thread.start() logger.info("Flask запущен") # Запуск периодического копирования start_periodic_backup() try: asyncio.run(dp.start_polling(bot, on_startup=on_startup)) except KeyboardInterrupt: logger.info("Остановка") finally: flask_thread.join()