flpolprojects commited on
Commit
bfb4d97
·
verified ·
1 Parent(s): 90b8920

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +231 -175
app.py CHANGED
@@ -18,46 +18,35 @@ logging.basicConfig(level=logging.INFO)
18
  logger = logging.getLogger(__name__)
19
 
20
  # Инициализация бота и Flask
21
- BOT_TOKEN = '7734802681:AAGKHGG8O9uNk64JWTHH5yqXzvSxCcoLUdA' # Замените на токен вашего бота
22
  bot = Bot(token=BOT_TOKEN)
23
  dp = Dispatcher()
24
  app = Flask(__name__)
25
 
26
- # Путь для хранения данных (товары, заказы и категории)
27
  DATA_FILE = 'data.json'
28
 
29
  # Настройки Hugging Face
30
- REPO_ID = "flpolprojects/Clients" # Замените на ваш репозиторий
31
- HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Установите переменную окружения HF_TOKEN
32
- HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") # Установите переменную окружения HF_TOKEN_READ
33
 
34
- # Функция для загрузки данных из Hugging Face Hub
35
  def load_data():
36
  try:
37
  download_db_from_hf()
38
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
39
  loaded_data = json.load(f)
40
- # Проверка структуры JSON: ожидается словарь с ключами 'products', 'orders' и 'categories'
41
  if not (isinstance(loaded_data, dict) and 'products' in loaded_data and 'orders' in loaded_data):
42
- logger.error("Неверная структура JSON файла, ожидается словарь с ключами 'products' и 'orders'. Используем дефолтное значение.")
43
  loaded_data = {'products': [], 'orders': []}
44
  if "categories" not in loaded_data:
45
  loaded_data["categories"] = []
46
  return loaded_data
47
- except FileNotFoundError:
48
- logger.warning("Локальный файл базы данных не найден после скачивания.")
49
- return {'products': [], 'orders': [], 'categories': []}
50
- except json.JSONDecodeError:
51
- logger.error("Ошибка при загрузке данных: Невозможно декодировать JSON файл.")
52
- return {'products': [], 'orders': [], 'categories': []}
53
- except RepositoryNotFoundError:
54
- logger.error("Репозиторий не найден. Создание локальной базы данных.")
55
- return {'products': [], 'orders': [], 'categories': []}
56
  except Exception as e:
57
  logger.error(f"Ошибка при загрузке данных: {e}")
58
  return {'products': [], 'orders': [], 'categories': []}
59
 
60
- # Функция для сохранения данных в Hugging Face Hub
61
  def save_data(data):
62
  try:
63
  with open(DATA_FILE, 'w', encoding='utf-8') as f:
@@ -66,7 +55,6 @@ def save_data(data):
66
  except Exception as e:
67
  logger.error(f"Ошибка при сохранении данных: {e}")
68
 
69
- # Функция для загрузки базы данных из Hugging Face Hub
70
  def upload_db_to_hf():
71
  try:
72
  api = HfApi()
@@ -76,13 +64,12 @@ def upload_db_to_hf():
76
  repo_id=REPO_ID,
77
  repo_type="dataset",
78
  token=HF_TOKEN_WRITE,
79
- commit_message=f"Автоматическое резервное копирование базы данных {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
80
  )
81
- logger.info("Резервная копия JSON базы успешно загружена на Hugging Face.")
82
  except Exception as e:
83
  logger.error(f"Ошибка при загрузке резервной копии: {e}")
84
 
85
- # Функция для скачивания базы данных из Hugging Face Hub
86
  def download_db_from_hf():
87
  try:
88
  hf_hub_download(
@@ -93,20 +80,16 @@ def download_db_from_hf():
93
  local_dir=".",
94
  local_dir_use_symlinks=False
95
  )
96
- logger.info("JSON база успешно скачана из Hugging Face.")
97
- except RepositoryNotFoundError as e:
98
- logger.error(f"Репозиторий не найден: {e}")
99
- raise
100
  except Exception as e:
101
- logger.error(f"Ошибка при скачивании JSON базы: {e}")
102
  raise
103
 
104
  # Загрузка данных
105
  data = load_data()
106
 
107
- # Функции для формирования клавиатур
108
  def get_main_keyboard():
109
- # Теперь кнопка "Меню" покажет категории товаров
110
  builder = ReplyKeyboardBuilder()
111
  builder.button(text="📋 Меню")
112
  builder.button(text="🛒 Корзина")
@@ -118,7 +101,6 @@ def get_category_keyboard():
118
  builder = InlineKeyboardBuilder()
119
  for category in data['categories']:
120
  builder.button(text=category['name'], callback_data=f"cat_{category['id']}")
121
- # Добавляем кнопку "Назад" для возврата в главное меню, если понадобится
122
  builder.adjust(2)
123
  return builder.as_markup()
124
 
@@ -127,12 +109,11 @@ def get_product_keyboard(product_id):
127
  builder.button(text="Добавить в корзину", callback_data=f"add_{product_id}")
128
  return builder.as_markup()
129
 
130
- # Обработчики для бота
131
  @dp.message(Command("start"))
132
  async def cmd_start(message: types.Message):
133
  await message.answer("Привет! Я твой бот-магазин. Выбери действие:", reply_markup=get_main_keyboard())
134
 
135
- # При нажатии на "Меню" выводятся категории
136
  @dp.message(F.text == "📋 Меню")
137
  async def show_categories(message: types.Message):
138
  if not data['categories']:
@@ -140,34 +121,43 @@ async def show_categories(message: types.Message):
140
  return
141
  await message.answer("Выберите категорию:", reply_markup=get_category_keyboard())
142
 
143
- # Обработчик для выбора категории (callback_data: "cat_{id}")
144
  @dp.callback_query(F.data.startswith("cat_"))
145
  async def show_products_in_category(callback_query: types.CallbackQuery):
146
  try:
147
  cat_id = int(callback_query.data.split('_')[1])
148
- except (IndexError, ValueError):
149
- await bot.answer_callback_query(callback_query.id, "Неверные данные категории")
150
- return
151
-
152
- # Фильтруем товары по выбранной категории
153
- products_in_cat = [p for p in data['products'] if p.get('category_id') == cat_id]
154
- if not products_in_cat:
155
- await bot.send_message(callback_query.from_user.id, этой категории нет товаров.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  await bot.answer_callback_query(callback_query.id)
157
- return
158
-
159
- for product in products_in_cat:
160
- photo_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{product['photo']}" if product.get('photo') else None
161
- caption = f"🏷 {product['name']} - {product['price']} руб.\nОписание: {product['description']}\n/id: {product['id']}"
162
- if photo_url:
163
- try:
164
- await bot.send_photo(chat_id=callback_query.from_user.id, photo=photo_url, caption=caption, reply_markup=get_product_keyboard(product['id']))
165
- except Exception as e:
166
- logger.error(f"Ошибка при отправке фото: {e}")
167
- await bot.send_message(callback_query.from_user.id, caption, reply_markup=get_product_keyboard(product['id']))
168
- else:
169
- await bot.send_message(callback_query.from_user.id, caption, reply_markup=get_product_keyboard(product['id']))
170
- await bot.answer_callback_query(callback_query.id)
171
 
172
  @dp.message(F.text == "🛒 Корзина")
173
  async def show_cart(message: types.Message):
@@ -180,9 +170,9 @@ async def show_cart(message: types.Message):
180
  response = "Ваша корзина:\n"
181
  for item in cart['items']:
182
  product = next(p for p in data['products'] if p['id'] == item['product_id'])
183
- response += f"🏷 {product['name']} - {product['price']} руб. x {item['quantity']}\n"
184
  total += product['price'] * item['quantity']
185
- response += f"\nИтого: {total} руб."
186
  builder = InlineKeyboardBuilder()
187
  builder.button(text="Оформить заказ", callback_data=f"complete_{user_id}")
188
  await message.answer(response, reply_markup=builder.as_markup())
@@ -191,55 +181,47 @@ async def show_cart(message: types.Message):
191
  async def add_to_cart(callback_query: types.CallbackQuery):
192
  try:
193
  product_id = int(callback_query.data.split('_')[1])
194
- except (IndexError, ValueError):
195
- await bot.answer_callback_query(callback_query.id, "Неверные данные товара")
196
- return
197
- product = next((p for p in data['products'] if p['id'] == product_id), None)
198
- if product:
199
- user_id = callback_query.from_user.id
200
- cart = next((o for o in data['orders'] if o['user_id'] == user_id and not o.get('completed')), None)
201
- if not cart:
202
- cart = {'user_id': user_id, 'items': [], 'date': datetime.now().isoformat()}
203
- data['orders'].append(cart)
204
- cart['items'].append({'product_id': product_id, 'quantity': 1})
205
- save_data(data)
206
- await bot.answer_callback_query(callback_query.id, "Товар добавлен в корзину!")
207
- else:
208
- await bot.answer_callback_query(callback_query.id, "Товар не найден.")
209
 
210
- # Изменённый обработчик "Оформить заказ" – заказ сразу отправляется в WhatsApp и удаляется из корзины
211
  @dp.callback_query(F.data.startswith("complete_"))
212
  async def complete_order(callback_query: types.CallbackQuery):
213
  try:
214
  user_id = int(callback_query.data.split('_')[1])
215
- except (IndexError, ValueError):
216
- await bot.answer_callback_query(callback_query.id, "Неверные данные")
217
- return
218
- cart = next((o for o in data['orders'] if o['user_id'] == user_id and not o.get('completed')), None)
219
- if cart and cart['items']:
220
- total = 0
221
- cart_text = "Привет, я хочу сделать заказ:\n"
222
- for item in cart['items']:
223
- product = next((p for p in data['products'] if p['id'] == item['product_id']), None)
224
- if product:
225
- cart_text += f"{product['name']} - {product['price']} руб. x {item['quantity']}\n"
226
- total += product['price'] * item['quantity']
227
- cart_text += f"\nИтого: {total} руб."
228
- encoded_text = urllib.parse.quote(cart_text)
229
- # Новый номер WhatsApp: +996709513331 (без знака + для ссылки)
230
- whatsapp_link = f"https://wa.me/996709513331?text={encoded_text}"
231
- # Удаляем корзину, чтобы заказ не попадал в личный кабинет
232
- data['orders'].remove(cart)
233
- save_data(data)
234
- await bot.send_message(user_id, f"Пожалуйста, оформите заказ через WhatsApp, перейдя по ссылке:\n{whatsapp_link}")
235
- await bot.answer_callback_query(callback_query.id)
236
- else:
237
- await bot.answer_callback_query(callback_query.id, "Корзина пуста или заказ уже оформлен.")
238
 
239
  @dp.message(F.text == "📦 Заказы")
240
  async def show_orders(message: types.Message):
241
  user_id = message.from_user.id
242
- # Так как оформленные заказы больше не сохраняются, здесь показываются только те, которые остались (если вдруг)
243
  user_orders = [o for o in data['orders'] if o.get('completed')]
244
  if not user_orders:
245
  await message.answer("У вас нет оформленных заказов.")
@@ -249,26 +231,84 @@ async def show_orders(message: types.Message):
249
  total = 0
250
  for item in order['items']:
251
  product = next(p for p in data['products'] if p['id'] == item['product_id'])
252
- response += f"🏷 {product['name']} - {product['price']} руб. x {item['quantity']}\n"
253
  total += product['price'] * item['quantity']
254
- response += f"\nИтого: {total} руб.\nДата: {order['date']}"
255
  await message.answer(response)
256
 
257
- # Админ-панель (Flask)
258
  admin_html = """
259
  <!DOCTYPE html>
260
  <html>
261
  <head>
262
  <title>Админ-панель</title>
 
263
  <style>
264
- body { font-family: Arial, sans-serif; margin: 20px; background-color: #f0f0f0; }
265
- .container { max-width: 800px; margin: 0 auto; }
266
- .section { background-color: #fff; padding: 15px; margin-bottom: 20px; border-radius: 5px; }
267
- input, textarea, select { width: 100%; margin: 5px 0; padding: 8px; }
268
- button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; }
269
- button:hover { background-color: #45a049; }
270
- img { max-width: 100px; max-height: 100px; }
271
- .item { border: 1px solid #ccc; padding: 10px; margin: 5px 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  </style>
273
  </head>
274
  <body>
@@ -277,7 +317,7 @@ admin_html = """
277
  <div class="section">
278
  <h2>Управление категориями</h2>
279
  <form id="addCategoryForm" method="POST" action="/add_category">
280
- <input type="text" name="name" placeholder="Название категории" required><br>
281
  <button type="submit">Добавить категорию</button>
282
  </form>
283
  <h3>Существующие категории</h3>
@@ -295,24 +335,24 @@ admin_html = """
295
  <div class="section">
296
  <h2>Управление товарами</h2>
297
  <form id="addProductForm" method="POST" enctype="multipart/form-data" action="/add_product">
298
- <input type="text" name="name" placeholder="Название" required><br>
299
- <input type="number" name="price" placeholder="Цена" step="0.01" required><br>
300
- <textarea name="description" placeholder="Описание" required></textarea><br>
301
  <label>Категория:</label>
302
  <select name="category_id" required>
303
  <option value="">Выберите категорию</option>
304
  {% for category in categories %}
305
  <option value="{{ category.id }}">{{ category.name }}</option>
306
  {% endfor %}
307
- </select><br>
308
- <input type="file" name="photo" accept="image/*"><br>
309
  <button type="submit">Добавить товар</button>
310
  </form>
311
  <h3>Существующие товары</h3>
312
  {% if products %}
313
  {% for product in products %}
314
  <div class="item">
315
- {{ product.name }} - {{ product.price }} руб.<br>
316
  {{ product.description }}<br>
317
  {% if product.photo %}
318
  <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product.photo }}" alt="{{ product.name }}">
@@ -331,7 +371,7 @@ admin_html = """
331
  <div class="item">
332
  Пользователь: {{ order.user_id }}<br>
333
  Дата: {{ order.date }}<br>
334
- Товары:
335
  {% for item in order['items'] %}
336
  {% for product in products %}
337
  {% if product.id == item.product_id %}
@@ -347,73 +387,81 @@ admin_html = """
347
  </div>
348
  </div>
349
  <script>
 
 
 
 
 
 
 
 
 
 
 
350
  async function deleteProduct(productId) {
351
  const response = await fetch(`/delete_product/${productId}`, { method: 'POST' });
352
- if (response.ok) window.location.reload();
353
  }
354
  async function deleteCategory(categoryId) {
355
  const response = await fetch(`/delete_category/${categoryId}`, { method: 'POST' });
356
- if (response.ok) window.location.reload();
 
 
 
357
  }
358
  </script>
359
  </body>
360
  </html>
361
  """
362
 
 
 
 
363
  @app.route('/')
364
  def admin_panel():
365
  try:
366
- logger.info("Rendering admin panel with products, orders and categories")
367
  return render_template_string(admin_html, products=data['products'], orders=data['orders'], categories=data['categories'], repo_id=REPO_ID)
368
  except Exception as e:
369
  logger.error(f"Ошибка в шаблоне: {e}")
370
- return "Ошибка сервера. Проверь логи.", 500
371
 
372
  @app.route('/add_product', methods=['POST'])
373
  def add_product():
374
  try:
375
- if request.method == 'POST':
376
- logger.info("Adding new product")
377
- name = request.form['name']
378
- price = float(request.form['price'])
379
- description = request.form['description']
380
- category_id = int(request.form['category_id'])
381
- photo = request.files.get('photo')
382
- product_id = max((p['id'] for p in data['products']), default=0) + 1
383
-
384
- photo_filename = None
385
- if photo and photo.filename:
386
- photo_filename = secure_filename(photo.filename)
387
- temp_path = os.path.join(".", photo_filename)
388
- photo.save(temp_path)
389
-
390
- try:
391
- api = HfApi()
392
- api.upload_file(
393
- path_or_fileobj=temp_path,
394
- path_in_repo=f"photos/{photo_filename}",
395
- repo_id=REPO_ID,
396
- repo_type="dataset",
397
- token=HF_TOKEN_WRITE,
398
- commit_message=f"Добавлено фото для товара {name}"
399
- )
400
- logger.info(f"Фото успешно загружено: {photo_filename}")
401
- except Exception as e:
402
- logger.error(f"Ошибка при загрузке фото: {e}")
403
- return jsonify({'status': 'error', 'message': f'Ошибка при загрузке фото: {str(e)}'}), 500
404
- finally:
405
- os.remove(temp_path)
406
-
407
- data['products'].append({
408
- 'id': product_id,
409
- 'name': name,
410
- 'price': price,
411
- 'description': description,
412
- 'category_id': category_id,
413
- 'photo': photo_filename
414
- })
415
- save_data(data)
416
- return redirect("/")
417
  except Exception as e:
418
  logger.error(f"Ошибка при добавлении товара: {e}")
419
  return jsonify({'status': 'error', 'message': str(e)}), 500
@@ -421,9 +469,9 @@ def add_product():
421
  @app.route('/delete_product/<int:product_id>', methods=['POST'])
422
  def delete_product(product_id):
423
  try:
424
- logger.info(f"Deleting product with id={product_id}")
425
  data['products'] = [p for p in data['products'] if p['id'] != product_id]
426
  save_data(data)
 
427
  return jsonify({'status': 'success'})
428
  except Exception as e:
429
  logger.error(f"Ошибка при удалении товара: {e}")
@@ -436,6 +484,7 @@ def add_category():
436
  category_id = max((c['id'] for c in data['categories']), default=0) + 1
437
  data['categories'].append({'id': category_id, 'name': name})
438
  save_data(data)
 
439
  return redirect("/")
440
  except Exception as e:
441
  logger.error(f"Ошибка при добавлении категории: {e}")
@@ -444,35 +493,42 @@ def add_category():
444
  @app.route('/delete_category/<int:category_id>', methods=['POST'])
445
  def delete_category(category_id):
446
  try:
447
- logger.info(f"Deleting category with id={category_id}")
448
  data['categories'] = [c for c in data['categories'] if c['id'] != category_id]
449
  save_data(data)
 
450
  return jsonify({'status': 'success'})
451
  except Exception as e:
452
  logger.error(f"Ошибка при удалении категории: {e}")
453
  return jsonify({'status': 'error', 'message': str(e)}), 500
454
 
455
- # Запуск бота и Flask
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  async def on_startup(_):
457
  logger.info("Бот запущен!")
458
 
459
  def run_flask():
460
- try:
461
- logger.info("Starting Flask server on port 7860")
462
- app.run(host='0.0.0.0', port=7860, debug=True, use_reloader=False)
463
- except Exception as e:
464
- logger.error(f"Ошибка в Flask: {e}")
465
 
466
  if __name__ == '__main__':
467
- # Создаём и запускаем поток для Flask
468
  flask_thread = threading.Thread(target=run_flask, daemon=True)
469
  flask_thread.start()
470
- logger.info("Flask thread started")
471
-
472
- # Запускаем бота в главном потоке
473
  try:
474
  asyncio.run(dp.start_polling(bot, on_startup=on_startup))
475
  except KeyboardInterrupt:
476
- logger.info("Stopping bot and Flask")
477
  finally:
478
  flask_thread.join()
 
18
  logger = logging.getLogger(__name__)
19
 
20
  # Инициализация бота и Flask
21
+ BOT_TOKEN = '7734802681:AAGKHGG8O9uNk64JWTHH5yqXzvSxCcoLUdA'
22
  bot = Bot(token=BOT_TOKEN)
23
  dp = Dispatcher()
24
  app = Flask(__name__)
25
 
26
+ # Путь для хранения данных
27
  DATA_FILE = 'data.json'
28
 
29
  # Настройки Hugging Face
30
+ REPO_ID = "flpolprojects/Clients"
31
+ HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
32
+ HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
33
 
34
+ # Функции для работы с данными
35
  def load_data():
36
  try:
37
  download_db_from_hf()
38
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
39
  loaded_data = json.load(f)
 
40
  if not (isinstance(loaded_data, dict) and 'products' in loaded_data and 'orders' in loaded_data):
41
+ logger.error("Неверная структура JSON файла")
42
  loaded_data = {'products': [], 'orders': []}
43
  if "categories" not in loaded_data:
44
  loaded_data["categories"] = []
45
  return loaded_data
 
 
 
 
 
 
 
 
 
46
  except Exception as e:
47
  logger.error(f"Ошибка при загрузке данных: {e}")
48
  return {'products': [], 'orders': [], 'categories': []}
49
 
 
50
  def save_data(data):
51
  try:
52
  with open(DATA_FILE, 'w', encoding='utf-8') as f:
 
55
  except Exception as e:
56
  logger.error(f"Ошибка при сохранении данных: {e}")
57
 
 
58
  def upload_db_to_hf():
59
  try:
60
  api = HfApi()
 
64
  repo_id=REPO_ID,
65
  repo_type="dataset",
66
  token=HF_TOKEN_WRITE,
67
+ commit_message=f"Автоматическое резервное копирование {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
68
  )
69
+ logger.info("База загружена на Hugging Face")
70
  except Exception as e:
71
  logger.error(f"Ошибка при загрузке резервной копии: {e}")
72
 
 
73
  def download_db_from_hf():
74
  try:
75
  hf_hub_download(
 
80
  local_dir=".",
81
  local_dir_use_symlinks=False
82
  )
83
+ logger.info("База скачана из Hugging Face")
 
 
 
84
  except Exception as e:
85
+ logger.error(f"Ошибка при скачивании: {e}")
86
  raise
87
 
88
  # Загрузка данных
89
  data = load_data()
90
 
91
+ # Формирование кла��иатур
92
  def get_main_keyboard():
 
93
  builder = ReplyKeyboardBuilder()
94
  builder.button(text="📋 Меню")
95
  builder.button(text="🛒 Корзина")
 
101
  builder = InlineKeyboardBuilder()
102
  for category in data['categories']:
103
  builder.button(text=category['name'], callback_data=f"cat_{category['id']}")
 
104
  builder.adjust(2)
105
  return builder.as_markup()
106
 
 
109
  builder.button(text="Добавить в корзину", callback_data=f"add_{product_id}")
110
  return builder.as_markup()
111
 
112
+ # Обработчики бота
113
  @dp.message(Command("start"))
114
  async def cmd_start(message: types.Message):
115
  await message.answer("Привет! Я твой бот-магазин. Выбери действие:", reply_markup=get_main_keyboard())
116
 
 
117
  @dp.message(F.text == "📋 Меню")
118
  async def show_categories(message: types.Message):
119
  if not data['categories']:
 
121
  return
122
  await message.answer("Выберите категорию:", reply_markup=get_category_keyboard())
123
 
124
+ # Оптимизированный вывод товаров
125
  @dp.callback_query(F.data.startswith("cat_"))
126
  async def show_products_in_category(callback_query: types.CallbackQuery):
127
  try:
128
  cat_id = int(callback_query.data.split('_')[1])
129
+ products_in_cat = [p for p in data['products'] if p.get('category_id') == cat_id]
130
+
131
+ if not products_in_cat:
132
+ await bot.send_message(callback_query.from_user.id, "В этой категории нет товаров.")
133
+ await bot.answer_callback_query(callback_query.id)
134
+ return
135
+
136
+ # Отправка товаров пачками асинхронно
137
+ async def send_product_batch(products_batch):
138
+ for product in products_batch:
139
+ photo_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{product['photo']}" if product.get('photo') else None
140
+ caption = f"🏷 {product['name']} - {product['price']} сом\nОписание: {product['description']}\n/id: {product['id']}"
141
+ try:
142
+ if photo_url:
143
+ await bot.send_photo(chat_id=callback_query.from_user.id, photo=photo_url, caption=caption, reply_markup=get_product_keyboard(product['id']))
144
+ else:
145
+ await bot.send_message(callback_query.from_user.id, caption, reply_markup=get_product_keyboard(product['id']))
146
+ except Exception as e:
147
+ logger.error(f"Ошибка при отправке: {e}")
148
+ await bot.send_message(callback_query.from_user.id, caption, reply_markup=get_product_keyboard(product['id']))
149
+
150
+ # Разбиваем на пачки по 5 товаров
151
+ batch_size = 5
152
+ for i in range(0, len(products_in_cat), batch_size):
153
+ batch = products_in_cat[i:i + batch_size]
154
+ await send_product_batch(batch)
155
+ await asyncio.sleep(0.1) # Небольшая задержка между пачками
156
+
157
  await bot.answer_callback_query(callback_query.id)
158
+ except Exception as e:
159
+ logger.error(f"Ошибка в show_products_in_category: {e}")
160
+ await bot.answer_callback_query(callback_query.id, "Ошибка при загрузке товаров")
 
 
 
 
 
 
 
 
 
 
 
161
 
162
  @dp.message(F.text == "🛒 Корзина")
163
  async def show_cart(message: types.Message):
 
170
  response = "Ваша корзина:\n"
171
  for item in cart['items']:
172
  product = next(p for p in data['products'] if p['id'] == item['product_id'])
173
+ response += f"🏷 {product['name']} - {product['price']} сом x {item['quantity']}\n"
174
  total += product['price'] * item['quantity']
175
+ response += f"\nИтого: {total} сом"
176
  builder = InlineKeyboardBuilder()
177
  builder.button(text="Оформить заказ", callback_data=f"complete_{user_id}")
178
  await message.answer(response, reply_markup=builder.as_markup())
 
181
  async def add_to_cart(callback_query: types.CallbackQuery):
182
  try:
183
  product_id = int(callback_query.data.split('_')[1])
184
+ product = next((p for p in data['products'] if p['id'] == product_id), None)
185
+ if product:
186
+ user_id = callback_query.from_user.id
187
+ cart = next((o for o in data['orders'] if o['user_id'] == user_id and not o.get('completed')), None)
188
+ if not cart:
189
+ cart = {'user_id': user_id, 'items': [], 'date': datetime.now().isoformat()}
190
+ data['orders'].append(cart)
191
+ cart['items'].append({'product_id': product_id, 'quantity': 1})
192
+ save_data(data)
193
+ await bot.answer_callback_query(callback_query.id, "Товар добавлен в корзину!")
194
+ except Exception as e:
195
+ logger.error(f"Ошибка при добавлении в корзину: {e}")
196
+ await bot.answer_callback_query(callback_query.id, "Ошибка")
 
 
197
 
 
198
  @dp.callback_query(F.data.startswith("complete_"))
199
  async def complete_order(callback_query: types.CallbackQuery):
200
  try:
201
  user_id = int(callback_query.data.split('_')[1])
202
+ cart = next((o for o in data['orders'] if o['user_id'] == user_id and not o.get('completed')), None)
203
+ if cart and cart['items']:
204
+ total = 0
205
+ cart_text = "Привет, я хочу сделать заказ:\n"
206
+ for item in cart['items']:
207
+ product = next((p for p in data['products'] if p['id'] == item['product_id']), None)
208
+ if product:
209
+ cart_text += f"{product['name']} - {product['price']} сом x {item['quantity']}\n"
210
+ total += product['price'] * item['quantity']
211
+ cart_text += f"\nИтого: {total} сом"
212
+ encoded_text = urllib.parse.quote(cart_text)
213
+ whatsapp_link = f"https://wa.me/996709513331?text={encoded_text}"
214
+ data['orders'].remove(cart)
215
+ save_data(data)
216
+ await bot.send_message(user_id, f"Оформите заказ через WhatsApp:\n{whatsapp_link}")
217
+ await bot.answer_callback_query(callback_query.id)
218
+ except Exception as e:
219
+ logger.error(f"Ошибка при оформлении заказа: {e}")
220
+ await bot.answer_callback_query(callback_query.id, "Ошибка")
 
 
 
 
221
 
222
  @dp.message(F.text == "📦 Заказы")
223
  async def show_orders(message: types.Message):
224
  user_id = message.from_user.id
 
225
  user_orders = [o for o in data['orders'] if o.get('completed')]
226
  if not user_orders:
227
  await message.answer("У вас нет оформленных заказов.")
 
231
  total = 0
232
  for item in order['items']:
233
  product = next(p for p in data['products'] if p['id'] == item['product_id'])
234
+ response += f"🏷 {product['name']} - {product['price']} сом x {item['quantity']}\n"
235
  total += product['price'] * item['quantity']
236
+ response += f"\nИтого: {total} сом\nДата: {order['date']}"
237
  await message.answer(response)
238
 
239
+ # Админ-панель с адаптивным дизайном
240
  admin_html = """
241
  <!DOCTYPE html>
242
  <html>
243
  <head>
244
  <title>Админ-панель</title>
245
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
246
  <style>
247
+ body {
248
+ font-family: Arial, sans-serif;
249
+ margin: 10px;
250
+ background-color: #f0f0f0;
251
+ }
252
+ .container {
253
+ max-width: 1000px;
254
+ margin: 0 auto;
255
+ }
256
+ .section {
257
+ background-color: #fff;
258
+ padding: 10px;
259
+ margin-bottom: 15px;
260
+ border-radius: 5px;
261
+ }
262
+ h1, h2, h3 {
263
+ margin: 10px 0;
264
+ }
265
+ input, textarea, select {
266
+ width: 100%;
267
+ margin: 5px 0;
268
+ padding: 8px;
269
+ box-sizing: border-box;
270
+ }
271
+ button {
272
+ background-color: #4CAF50;
273
+ color: white;
274
+ padding: 8px 12px;
275
+ border: none;
276
+ border-radius: 5px;
277
+ cursor: pointer;
278
+ width: 100%;
279
+ margin: 5px 0;
280
+ }
281
+ button:hover {
282
+ background-color: #45a049;
283
+ }
284
+ img {
285
+ max-width: 100%;
286
+ height: auto;
287
+ max-height: 150px;
288
+ }
289
+ .item {
290
+ border: 1px solid #ccc;
291
+ padding: 10px;
292
+ margin: 5px 0;
293
+ word-wrap: break-word;
294
+ }
295
+ @media (max-width: 600px) {
296
+ .section {
297
+ padding: 8px;
298
+ }
299
+ button {
300
+ padding: 6px 10px;
301
+ }
302
+ h1 {
303
+ font-size: 1.5em;
304
+ }
305
+ h2 {
306
+ font-size: 1.2em;
307
+ }
308
+ h3 {
309
+ font-size: 1em;
310
+ }
311
+ }
312
  </style>
313
  </head>
314
  <body>
 
317
  <div class="section">
318
  <h2>Управление категориями</h2>
319
  <form id="addCategoryForm" method="POST" action="/add_category">
320
+ <input type="text" name="name" placeholder="Название категории" required>
321
  <button type="submit">Добавить категорию</button>
322
  </form>
323
  <h3>Существующие категории</h3>
 
335
  <div class="section">
336
  <h2>Управление товарами</h2>
337
  <form id="addProductForm" method="POST" enctype="multipart/form-data" action="/add_product">
338
+ <input type="text" name="name" placeholder="Название" required>
339
+ <input type="number" name="price" placeholder="Цена" step="0.01" required>
340
+ <textarea name="description" placeholder="Описание" required></textarea>
341
  <label>Категория:</label>
342
  <select name="category_id" required>
343
  <option value="">Выберите категорию</option>
344
  {% for category in categories %}
345
  <option value="{{ category.id }}">{{ category.name }}</option>
346
  {% endfor %}
347
+ </select>
348
+ <input type="file" name="photo" accept="image/*">
349
  <button type="submit">Добавить товар</button>
350
  </form>
351
  <h3>Существующие товары</h3>
352
  {% if products %}
353
  {% for product in products %}
354
  <div class="item">
355
+ {{ product.name }} - {{ product.price }} сом<br>
356
  {{ product.description }}<br>
357
  {% if product.photo %}
358
  <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product.photo }}" alt="{{ product.name }}">
 
371
  <div class="item">
372
  Пользователь: {{ order.user_id }}<br>
373
  Дата: {{ order.date }}<br>
374
+ Товары:
375
  {% for item in order['items'] %}
376
  {% for product in products %}
377
  {% if product.id == item.product_id %}
 
387
  </div>
388
  </div>
389
  <script>
390
+ // Автоматическое обновление через Server-Sent Events
391
+ const eventSource = new EventSource('/updates');
392
+ eventSource.onmessage = function(event) {
393
+ if (event.data === 'update') {
394
+ window.location.reload();
395
+ }
396
+ };
397
+ eventSource.onerror = function() {
398
+ console.log("Ошибка SSE, reconnecting...");
399
+ };
400
+
401
  async function deleteProduct(productId) {
402
  const response = await fetch(`/delete_product/${productId}`, { method: 'POST' });
403
+ if (response.ok) broadcastUpdate();
404
  }
405
  async function deleteCategory(categoryId) {
406
  const response = await fetch(`/delete_category/${categoryId}`, { method: 'POST' });
407
+ if (response.ok) broadcastUpdate();
408
+ }
409
+ function broadcastUpdate() {
410
+ fetch('/broadcast_update', { method: 'POST' });
411
  }
412
  </script>
413
  </body>
414
  </html>
415
  """
416
 
417
+ # Глобальная переменная для отслеживания обновлений
418
+ update_event = threading.Event()
419
+
420
  @app.route('/')
421
  def admin_panel():
422
  try:
 
423
  return render_template_string(admin_html, products=data['products'], orders=data['orders'], categories=data['categories'], repo_id=REPO_ID)
424
  except Exception as e:
425
  logger.error(f"Ошибка в шаблоне: {e}")
426
+ return "Ошибка сервера", 500
427
 
428
  @app.route('/add_product', methods=['POST'])
429
  def add_product():
430
  try:
431
+ name = request.form['name']
432
+ price = float(request.form['price'])
433
+ description = request.form['description']
434
+ category_id = int(request.form['category_id'])
435
+ photo = request.files.get('photo')
436
+ product_id = max((p['id'] for p in data['products']), default=0) + 1
437
+
438
+ photo_filename = None
439
+ if photo and photo.filename:
440
+ photo_filename = secure_filename(photo.filename)
441
+ temp_path = os.path.join(".", photo_filename)
442
+ photo.save(temp_path)
443
+ api = HfApi()
444
+ api.upload_file(
445
+ path_or_fileobj=temp_path,
446
+ path_in_repo=f"photos/{photo_filename}",
447
+ repo_id=REPO_ID,
448
+ repo_type="dataset",
449
+ token=HF_TOKEN_WRITE,
450
+ commit_message=f"Добавлено фото для товара {name}"
451
+ )
452
+ os.remove(temp_path)
453
+
454
+ data['products'].append({
455
+ 'id': product_id,
456
+ 'name': name,
457
+ 'price': price,
458
+ 'description': description,
459
+ 'category_id': category_id,
460
+ 'photo': photo_filename
461
+ })
462
+ save_data(data)
463
+ update_event.set() # Сигнал об обновлении
464
+ return redirect("/")
 
 
 
 
 
 
 
 
465
  except Exception as e:
466
  logger.error(f"Ошибка при добавлении товара: {e}")
467
  return jsonify({'status': 'error', 'message': str(e)}), 500
 
469
  @app.route('/delete_product/<int:product_id>', methods=['POST'])
470
  def delete_product(product_id):
471
  try:
 
472
  data['products'] = [p for p in data['products'] if p['id'] != product_id]
473
  save_data(data)
474
+ update_event.set()
475
  return jsonify({'status': 'success'})
476
  except Exception as e:
477
  logger.error(f"Ошибка при удалении товара: {e}")
 
484
  category_id = max((c['id'] for c in data['categories']), default=0) + 1
485
  data['categories'].append({'id': category_id, 'name': name})
486
  save_data(data)
487
+ update_event.set()
488
  return redirect("/")
489
  except Exception as e:
490
  logger.error(f"Ошибка при добавлении категории: {e}")
 
493
  @app.route('/delete_category/<int:category_id>', methods=['POST'])
494
  def delete_category(category_id):
495
  try:
 
496
  data['categories'] = [c for c in data['categories'] if c['id'] != category_id]
497
  save_data(data)
498
+ update_event.set()
499
  return jsonify({'status': 'success'})
500
  except Exception as e:
501
  logger.error(f"Ошибка при удалении категории: {e}")
502
  return jsonify({'status': 'error', 'message': str(e)}), 500
503
 
504
+ @app.route('/updates')
505
+ def sse_updates():
506
+ def stream():
507
+ while True:
508
+ update_event.wait()
509
+ yield "data: update\n\n"
510
+ update_event.clear()
511
+ return app.response_class(stream(), mimetype="text/event-stream")
512
+
513
+ @app.route('/broadcast_update', methods=['POST'])
514
+ def broadcast_update():
515
+ update_event.set()
516
+ return jsonify({'status': 'success'})
517
+
518
+ # Запуск
519
  async def on_startup(_):
520
  logger.info("Бот запущен!")
521
 
522
  def run_flask():
523
+ app.run(host='0.0.0.0', port=7860, debug=True, use_reloader=False)
 
 
 
 
524
 
525
  if __name__ == '__main__':
 
526
  flask_thread = threading.Thread(target=run_flask, daemon=True)
527
  flask_thread.start()
528
+ logger.info("Flask запущен")
 
 
529
  try:
530
  asyncio.run(dp.start_polling(bot, on_startup=on_startup))
531
  except KeyboardInterrupt:
532
+ logger.info("Остановка")
533
  finally:
534
  flask_thread.join()