flpolprojects commited on
Commit
d31cc6e
·
verified ·
1 Parent(s): 6083e90

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +345 -191
app.py CHANGED
@@ -6,7 +6,7 @@ from datetime import datetime
6
  from aiogram import Bot, Dispatcher, types, F
7
  from aiogram.filters import Command
8
  from aiogram.utils.keyboard import ReplyKeyboardBuilder, InlineKeyboardBuilder
9
- from flask import Flask, request, jsonify, render_template_string
10
  import logging
11
  import threading
12
  from huggingface_hub import HfApi, hf_hub_download
@@ -18,44 +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'
41
  if not (isinstance(loaded_data, dict) and 'products' in loaded_data and 'orders' in loaded_data):
42
- logger.error("Неверная структура JSON файла, ожидается словарь с ключами 'products' и 'orders'. Используем дефолтное значение.")
43
- return {'products': [], 'orders': []}
 
 
44
  return loaded_data
45
- except FileNotFoundError:
46
- logger.warning("Локальный файл базы данных не найден после скачивания.")
47
- return {'products': [], 'orders': []}
48
- except json.JSONDecodeError:
49
- logger.error("Ошибка при загрузке данных: Невозможно декодировать JSON файл.")
50
- return {'products': [], 'orders': []}
51
- except RepositoryNotFoundError:
52
- logger.error("Репозиторий не найден. Создание локальной базы данных.")
53
- return {'products': [], 'orders': []}
54
  except Exception as e:
55
  logger.error(f"Ошибка при загрузке данных: {e}")
56
- return {'products': [], 'orders': []}
57
 
58
- # Функция для сохранения данных в Hugging Face Hub
59
  def save_data(data):
60
  try:
61
  with open(DATA_FILE, 'w', encoding='utf-8') as f:
@@ -64,7 +55,6 @@ def save_data(data):
64
  except Exception as e:
65
  logger.error(f"Ошибка при сохранении данных: {e}")
66
 
67
- # Функция для загрузки базы данных из Hugging Face Hub
68
  def upload_db_to_hf():
69
  try:
70
  api = HfApi()
@@ -74,13 +64,12 @@ def upload_db_to_hf():
74
  repo_id=REPO_ID,
75
  repo_type="dataset",
76
  token=HF_TOKEN_WRITE,
77
- commit_message=f"Автоматическое резервное копирование базы данных {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
78
  )
79
- logger.info("Резервная копия JSON базы успешно загружена на Hugging Face.")
80
  except Exception as e:
81
  logger.error(f"Ошибка при загрузке резервной копии: {e}")
82
 
83
- # Функция для скачивания базы данных из Hugging Face Hub
84
  def download_db_from_hf():
85
  try:
86
  hf_hub_download(
@@ -91,18 +80,15 @@ def download_db_from_hf():
91
  local_dir=".",
92
  local_dir_use_symlinks=False
93
  )
94
- logger.info("JSON база успешно скачана из Hugging Face.")
95
- except RepositoryNotFoundError as e:
96
- logger.error(f"Репозиторий не найден: {e}")
97
- raise
98
  except Exception as e:
99
- logger.error(f"Ошибка при скачивании JSON базы: {e}")
100
  raise
101
 
102
  # Загрузка данных
103
  data = load_data()
104
 
105
- # Клавиатуры
106
  def get_main_keyboard():
107
  builder = ReplyKeyboardBuilder()
108
  builder.button(text="📋 Меню")
@@ -111,32 +97,67 @@ def get_main_keyboard():
111
  builder.adjust(2)
112
  return builder.as_markup(resize_keyboard=True)
113
 
 
 
 
 
 
 
 
114
  def get_product_keyboard(product_id):
115
  builder = InlineKeyboardBuilder()
116
  builder.button(text="Добавить в корзину", callback_data=f"add_{product_id}")
117
  return builder.as_markup()
118
 
119
- # Обработчики для бота
120
  @dp.message(Command("start"))
121
  async def cmd_start(message: types.Message):
122
  await message.answer("Привет! Я твой бот-магазин. Выбери действие:", reply_markup=get_main_keyboard())
123
 
124
  @dp.message(F.text == "📋 Меню")
125
- async def show_products(message: types.Message):
126
- if not data['products']:
127
- await message.answer("Нет доступных товаров.")
128
  return
129
- for product in data['products']:
130
- photo_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{product['photo']}" if product.get('photo') else None
131
- caption = f"🏷 {product['name']} - {product['price']} руб.\nОписание: {product['description']}\n/id: {product['id']}"
132
- if photo_url:
133
- try:
134
- await bot.send_photo(chat_id=message.chat.id, photo=photo_url, caption=caption, reply_markup=get_product_keyboard(product['id']))
135
- except Exception as e:
136
- logger.error(f"Ошибка при отправке фото: {e}")
137
- await message.answer(caption, reply_markup=get_product_keyboard(product['id']))
138
- else:
139
- await message.answer(caption, reply_markup=get_product_keyboard(product['id']))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
  @dp.message(F.text == "🛒 Корзина")
142
  async def show_cart(message: types.Message):
@@ -149,58 +170,58 @@ async def show_cart(message: types.Message):
149
  response = "Ваша корзина:\n"
150
  for item in cart['items']:
151
  product = next(p for p in data['products'] if p['id'] == item['product_id'])
152
- response += f"🏷 {product['name']} - {product['price']} руб. x {item['quantity']}\n"
153
  total += product['price'] * item['quantity']
154
- response += f"\nИтого: {total} руб."
155
  builder = InlineKeyboardBuilder()
156
  builder.button(text="Оформить заказ", callback_data=f"complete_{user_id}")
157
  await message.answer(response, reply_markup=builder.as_markup())
158
 
159
  @dp.callback_query(F.data.startswith("add_"))
160
  async def add_to_cart(callback_query: types.CallbackQuery):
161
- product_id = int(callback_query.data.split('_')[1])
162
- product = next((p for p in data['products'] if p['id'] == product_id), None)
163
- if product:
164
- user_id = callback_query.from_user.id
165
- cart = next((o for o in data['orders'] if o['user_id'] == user_id and not o.get('completed')), None)
166
- if not cart:
167
- cart = {'user_id': user_id, 'items': [], 'date': datetime.now().isoformat()}
168
- data['orders'].append(cart)
169
- cart['items'].append({'product_id': product_id, 'quantity': 1})
170
- save_data(data)
171
- await bot.answer_callback_query(callback_query.id, "Товар добавлен в корзину!")
172
- else:
173
- await bot.answer_callback_query(callback_query.id, "Товар не найден.")
 
 
174
 
175
- # Изменённый обработчик "Оформить заказ" – заказ сразу отправляется в WhatsApp и удаляется из корзины
176
  @dp.callback_query(F.data.startswith("complete_"))
177
  async def complete_order(callback_query: types.CallbackQuery):
178
- user_id = int(callback_query.data.split('_')[1])
179
- cart = next((o for o in data['orders'] if o['user_id'] == user_id and not o.get('completed')), None)
180
- if cart and cart['items']:
181
- total = 0
182
- cart_text = "Привет, я хочу сделать заказ:\n"
183
- for item in cart['items']:
184
- product = next((p for p in data['products'] if p['id'] == item['product_id']), None)
185
- if product:
186
- cart_text += f"{product['name']} - {product['price']} руб. x {item['quantity']}\n"
187
- total += product['price'] * item['quantity']
188
- cart_text += f"\nИтого: {total} руб."
189
- encoded_text = urllib.parse.quote(cart_text)
190
- whatsapp_link = f"https://wa.me/996500398754?text={encoded_text}"
191
- # Удаляем корзину, чтобы заказ не попадал в личный кабинет
192
- data['orders'].remove(cart)
193
- save_data(data)
194
- # Отправляем пользователю сообщение с ссылкой на WhatsApp
195
- await bot.send_message(user_id, f"Пожалуйста, оформите заказ через WhatsApp, перейдя по ссылке:\n{whatsapp_link}")
196
- await bot.answer_callback_query(callback_query.id)
197
- else:
198
- await bot.answer_callback_query(callback_query.id, "Корзина пуста или заказ уже оформлен.")
199
 
200
  @dp.message(F.text == "📦 Заказы")
201
  async def show_orders(message: types.Message):
202
  user_id = message.from_user.id
203
- # Поскольку оформленные заказы больше не сохраняются, здесь отображаются только те, которые остались в базе (если вдруг)
204
  user_orders = [o for o in data['orders'] if o.get('completed')]
205
  if not user_orders:
206
  await message.answer("У вас нет оформленных заказов.")
@@ -210,135 +231,237 @@ async def show_orders(message: types.Message):
210
  total = 0
211
  for item in order['items']:
212
  product = next(p for p in data['products'] if p['id'] == item['product_id'])
213
- response += f"🏷 {product['name']} - {product['price']} руб. x {item['quantity']}\n"
214
  total += product['price'] * item['quantity']
215
- response += f"\nИтого: {total} руб.\nДата: {order['date']}"
216
  await message.answer(response)
217
 
218
- # Админ-панель (Flask) с HTML, CSS и JavaScript в строке
219
  admin_html = """
220
  <!DOCTYPE html>
221
  <html>
222
  <head>
223
  <title>Админ-панель</title>
 
224
  <style>
225
- body { font-family: Arial, sans-serif; margin: 20px; background-color: #f0f0f0; }
226
- .container { max-width: 800px; margin: 0 auto; }
227
- .product { border: 1px solid #ccc; padding: 15px; margin: 10px 0; background-color: #fff; border-radius: 5px; }
228
- form { background-color: #fff; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
229
- input, textarea { width: 100%; margin: 5px 0; padding: 8px; }
230
- button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; }
231
- button:hover { background-color: #45a049; }
232
- img { max-width: 100px; max-height: 100px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  </style>
234
  </head>
235
  <body>
236
  <div class="container">
237
- <h1>Управление товарами</h1>
238
- <form id="addProductForm" method="POST" enctype="multipart/form-data" action="/add_product">
239
- <input type="text" name="name" placeholder="Название" required><br>
240
- <input type="number" name="price" placeholder="Цена" step="0.01" required><br>
241
- <textarea name="description" placeholder="Описание" required></textarea><br>
242
- <input type="file" name="photo" accept="image/*"><br>
243
- <button type="submit">Добавить товар</button>
244
- </form>
245
- <h2>Существующие товары</h2>
246
- {% if products %}
247
- {% for product in products %}
248
- <div class="product">
249
- {{ product.name }} - {{ product.price }} руб.<br>
250
- {{ product.description }}<br>
251
- {% if product.photo %}
252
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product.photo }}" alt="{{ product.name }}">
253
- {% endif %}
254
- <button onclick="deleteProduct({{ product.id }})">Удалить</button>
255
- </div>
256
- {% endfor %}
257
- {% else %}
258
- <p>Нет товаров.</p>
259
- {% endif %}
260
- <h2>Заказы</h2>
261
- {% if orders %}
262
- {% for order in orders %}
263
- <div class="product">
264
- Пользователь: {{ order.user_id }}<br>
265
- Дата: {{ order.date }}<br>
266
- Товары:
267
- {% for item in order['items'] %}
268
- {% for product in products %}
269
- {% if product.id == item.product_id %}
270
- {{ item.quantity }} x {{ product.name }}<br>
271
- {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  {% endfor %}
 
273
  {% endfor %}
274
- </div>
275
- {% endfor %}
276
- {% else %}
277
- <p>Нет заказов.</p>
278
- {% endif %}
279
  </div>
280
  <script>
 
 
 
 
 
 
 
 
 
 
 
281
  async function deleteProduct(productId) {
282
  const response = await fetch(`/delete_product/${productId}`, { method: 'POST' });
283
- if (response.ok) window.location.reload();
 
 
 
 
 
 
 
284
  }
285
  </script>
286
  </body>
287
  </html>
288
  """
289
 
 
 
 
290
  @app.route('/')
291
  def admin_panel():
292
  try:
293
- logger.info("Rendering admin panel with products and orders")
294
- return render_template_string(admin_html, products=data['products'], orders=data['orders'], repo_id=REPO_ID)
295
  except Exception as e:
296
  logger.error(f"Ошибка в шаблоне: {e}")
297
- return "Ошибка сервера. Проверь логи.", 500
298
 
299
  @app.route('/add_product', methods=['POST'])
300
  def add_product():
301
  try:
302
- if request.method == 'POST':
303
- logger.info("Adding new product")
304
- name = request.form['name']
305
- price = float(request.form['price'])
306
- description = request.form['description']
307
- photo = request.files['photo'] # Получаем файл фотографии
308
- product_id = max((p['id'] for p in data['products']), default=0) + 1
309
-
310
- photo_filename = None
311
- if photo:
312
- photo_filename = secure_filename(photo.filename)
313
- temp_path = os.path.join(".", photo_filename) # Сохраняем временно локально
314
- photo.save(temp_path)
315
 
316
- try:
317
- api = HfApi()
318
- api.upload_file(
319
- path_or_fileobj=temp_path,
320
- path_in_repo=f"photos/{photo_filename}",
321
- repo_id=REPO_ID,
322
- repo_type="dataset",
323
- token=HF_TOKEN_WRITE,
324
- commit_message=f"Добавлено фото для товара {name}"
325
- )
326
- logger.info(f"Фото успешно загружено: {photo_filename}")
327
- except Exception as e:
328
- logger.error(f"Ошибка при загрузке фото: {e}")
329
- return jsonify({'status': 'error', 'message': f'Ошибка при загрузке фото: {str(e)}'}), 500
330
- finally:
331
- os.remove(temp_path) # Удаляем временный файл
332
-
333
- data['products'].append({
334
- 'id': product_id,
335
- 'name': name,
336
- 'price': price,
337
- 'description': description,
338
- 'photo': photo_filename
339
- })
340
- save_data(data)
341
- return jsonify({'status': 'success'})
 
342
  except Exception as e:
343
  logger.error(f"Ошибка пр�� добавлении товара: {e}")
344
  return jsonify({'status': 'error', 'message': str(e)}), 500
@@ -346,35 +469,66 @@ def add_product():
346
  @app.route('/delete_product/<int:product_id>', methods=['POST'])
347
  def delete_product(product_id):
348
  try:
349
- logger.info(f"Deleting product with id={product_id}")
350
  data['products'] = [p for p in data['products'] if p['id'] != product_id]
351
  save_data(data)
 
352
  return jsonify({'status': 'success'})
353
  except Exception as e:
354
  logger.error(f"Ошибка при удалении товара: {e}")
355
  return jsonify({'status': 'error', 'message': str(e)}), 500
356
 
357
- # Запуск бота и Flask
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  async def on_startup(_):
359
  logger.info("Бот запущен!")
360
 
361
  def run_flask():
362
- try:
363
- logger.info("Starting Flask server on port 7860")
364
- app.run(host='0.0.0.0', port=7860, debug=True, use_reloader=False)
365
- except Exception as e:
366
- logger.error(f"Ошибка в Flask: {e}")
367
 
368
  if __name__ == '__main__':
369
- # Создаём и запускаем поток для Flask
370
  flask_thread = threading.Thread(target=run_flask, daemon=True)
371
  flask_thread.start()
372
- logger.info("Flask thread started")
373
-
374
- # Запускаем бота в главном потоке
375
  try:
376
  asyncio.run(dp.start_polling(bot, on_startup=on_startup))
377
  except KeyboardInterrupt:
378
- logger.info("Stopping bot and Flask")
379
  finally:
380
- flask_thread.join() # Ждём завершения потока Flask при завершении программы
 
6
  from aiogram import Bot, Dispatcher, types, F
7
  from aiogram.filters import Command
8
  from aiogram.utils.keyboard import ReplyKeyboardBuilder, InlineKeyboardBuilder
9
+ from flask import Flask, request, jsonify, render_template_string, redirect
10
  import logging
11
  import threading
12
  from huggingface_hub import HfApi, hf_hub_download
 
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="📋 Меню")
 
97
  builder.adjust(2)
98
  return builder.as_markup(resize_keyboard=True)
99
 
100
+ def get_category_keyboard():
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
+
107
  def get_product_keyboard(product_id):
108
  builder = InlineKeyboardBuilder()
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']:
120
+ await message.answer("Нет доступных категорий.")
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())
179
 
180
  @dp.callback_query(F.data.startswith("add_"))
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>
315
  <div class="container">
316
+ <h1>Админ-панель</h1>
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>
324
+ {% if categories %}
325
+ {% for category in categories %}
326
+ <div class="item">
327
+ {{ category.name }} (ID: {{ category.id }})
328
+ <button onclick="deleteCategory({{ category.id }})">Удалить</button>
329
+ </div>
330
+ {% endfor %}
331
+ {% else %}
332
+ <p>Нет категорий.</p>
333
+ {% endif %}
334
+ </div>
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 }}">
359
+ {% endif %}
360
+ <button onclick="deleteProduct({{ product.id }})">Удалить</button>
361
+ </div>
362
+ {% endfor %}
363
+ {% else %}
364
+ <p>Нет товаров.</p>
365
+ {% endif %}
366
+ </div>
367
+ <div class="section">
368
+ <h2>Заказы</h2>
369
+ {% if orders %}
370
+ {% for order in orders %}
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 %}
378
+ {{ item.quantity }} x {{ product.name }}<br>
379
+ {% endif %}
380
+ {% endfor %}
381
  {% endfor %}
382
+ </div>
383
  {% endfor %}
384
+ {% else %}
385
+ <p>Нет заказов.</p>
386
+ {% endif %}
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}")
478
  return jsonify({'status': 'error', 'message': str(e)}), 500
479
 
480
+ @app.route('/add_category', methods=['POST'])
481
+ def add_category():
482
+ try:
483
+ name = request.form['name']
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}")
491
+ return jsonify({'status': 'error', 'message': str(e)}), 500
492
+
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()