Update app.py
Browse files
app.py
CHANGED
|
@@ -26,7 +26,7 @@ app = Flask(__name__)
|
|
| 26 |
|
| 27 |
DATA_FILE = 'datatestoboto.json'
|
| 28 |
|
| 29 |
-
REPO_ID = "Kgshop/bottest"
|
| 30 |
HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
|
| 31 |
HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
|
| 32 |
|
|
@@ -51,7 +51,32 @@ def load_data():
|
|
| 51 |
return loaded_data
|
| 52 |
except Exception as e:
|
| 53 |
logger.error(f"Ошибка при загрузке данных: {e}")
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
def save_data(data):
|
| 57 |
try:
|
|
@@ -65,6 +90,10 @@ def upload_db_to_hf():
|
|
| 65 |
if not HF_TOKEN_WRITE:
|
| 66 |
logger.warning("HF_TOKEN_WRITE не установлен. Пропуск загрузки на Hugging Face.")
|
| 67 |
return
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
api = HfApi()
|
| 69 |
api.upload_file(
|
| 70 |
path_or_fileobj=DATA_FILE,
|
|
@@ -95,10 +124,7 @@ def download_db_from_hf():
|
|
| 95 |
logger.info("База скачана из Hugging Face")
|
| 96 |
except Exception as e:
|
| 97 |
logger.error(f"Ошибка при скачивании: {e}")
|
| 98 |
-
|
| 99 |
-
raise
|
| 100 |
-
else:
|
| 101 |
-
logger.warning(f"Продолжение работы с локальной базой ({DATA_FILE}) из-за ошибки скачивания.")
|
| 102 |
|
| 103 |
def start_periodic_backup():
|
| 104 |
def backup_loop():
|
|
@@ -132,9 +158,10 @@ def get_product_keyboard(product_id):
|
|
| 132 |
@dp.message(Command("start"))
|
| 133 |
async def cmd_start(message: types.Message):
|
| 134 |
user_id = message.from_user.id
|
| 135 |
-
if user_id not in data
|
| 136 |
data['users'].append(user_id)
|
| 137 |
save_data(data)
|
|
|
|
| 138 |
logger.info(f"Новый пользователь добавлен: {user_id}")
|
| 139 |
await message.answer("Здравствуйте ! это магазин Routine!. Выберите действие:", reply_markup=get_main_keyboard())
|
| 140 |
|
|
@@ -151,9 +178,10 @@ async def show_products_in_category(callback_query: types.CallbackQuery):
|
|
| 151 |
cat_id = int(callback_query.data.split('_')[1])
|
| 152 |
products_in_cat = [p for p in data['products'] if p.get('category_id') == cat_id]
|
| 153 |
|
|
|
|
|
|
|
| 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 |
async def send_product_batch(products_batch):
|
|
@@ -169,36 +197,39 @@ async def show_products_in_category(callback_query: types.CallbackQuery):
|
|
| 169 |
media_group.append(InputMediaPhoto(media=photo_url, caption=caption))
|
| 170 |
else:
|
| 171 |
media_group.append(InputMediaPhoto(media=photo_url))
|
| 172 |
-
|
|
|
|
|
|
|
| 173 |
await bot.send_message(callback_query.from_user.id, "Выберите действие:", reply_markup=get_product_keyboard(product['id']))
|
| 174 |
else:
|
| 175 |
await bot.send_message(callback_query.from_user.id, caption, reply_markup=get_product_keyboard(product['id']))
|
| 176 |
except Exception as e:
|
| 177 |
-
logger.error(f"Ошибка при отправке медиа или сообщения: {e}")
|
|
|
|
| 178 |
await bot.send_message(callback_query.from_user.id, caption, reply_markup=get_product_keyboard(product['id']))
|
| 179 |
|
| 180 |
-
batch_size =
|
| 181 |
for i in range(0, len(products_in_cat), batch_size):
|
| 182 |
batch = products_in_cat[i:i + batch_size]
|
| 183 |
await send_product_batch(batch)
|
| 184 |
-
await asyncio.sleep(0.
|
| 185 |
|
| 186 |
-
await bot.answer_callback_query(callback_query.id)
|
| 187 |
except Exception as e:
|
| 188 |
logger.error(f"Ошибка в show_products_in_category: {e}")
|
| 189 |
-
await bot.
|
|
|
|
| 190 |
|
| 191 |
@dp.message(F.text == "🛒 Корзина")
|
| 192 |
async def show_cart(message: types.Message):
|
| 193 |
user_id = message.from_user.id
|
| 194 |
-
cart = next((o for o in data
|
| 195 |
if not cart or not cart['items']:
|
| 196 |
await message.answer("Ваша корзина пуста.")
|
| 197 |
return
|
| 198 |
total = 0
|
| 199 |
response = "Ваша корзина:\n"
|
| 200 |
for item in cart['items']:
|
| 201 |
-
product = next((p for p in data
|
| 202 |
if product:
|
| 203 |
response += f"🏷 {product['name']} - {product['price']} сом x {item['quantity']}\n"
|
| 204 |
total += product['price'] * item['quantity']
|
|
@@ -229,15 +260,16 @@ async def confirm_add_to_cart(callback_query: types.CallbackQuery):
|
|
| 229 |
parts = callback_query.data.split('_')
|
| 230 |
product_id = int(parts[1])
|
| 231 |
quantity = int(parts[2])
|
| 232 |
-
product = next((p for p in data
|
| 233 |
if product:
|
| 234 |
user_id = callback_query.from_user.id
|
| 235 |
-
cart = next((o for o in data
|
| 236 |
if not cart:
|
| 237 |
cart = {'user_id': user_id, 'items': [], 'date': datetime.now().isoformat(), 'completed': False}
|
| 238 |
data['orders'].append(cart)
|
| 239 |
cart['items'].append({'product_id': product_id, 'quantity': quantity})
|
| 240 |
save_data(data)
|
|
|
|
| 241 |
await bot.answer_callback_query(callback_query.id, "Товар добавлен в корзину!")
|
| 242 |
else:
|
| 243 |
await bot.answer_callback_query(callback_query.id, "Товар не найден")
|
|
@@ -249,12 +281,12 @@ async def confirm_add_to_cart(callback_query: types.CallbackQuery):
|
|
| 249 |
async def complete_order(callback_query: types.CallbackQuery):
|
| 250 |
try:
|
| 251 |
user_id = int(callback_query.data.split('_')[1])
|
| 252 |
-
cart = next((o for o in data
|
| 253 |
if cart and cart['items']:
|
| 254 |
total = 0
|
| 255 |
cart_text = "Привет, я хочу сделать заказ:\n"
|
| 256 |
for item in cart['items']:
|
| 257 |
-
product = next((p for p in data
|
| 258 |
if product:
|
| 259 |
cart_text += f"{product['name']} - {product['price']} сом x {item['quantity']}\n"
|
| 260 |
total += product['price'] * item['quantity']
|
|
@@ -265,6 +297,8 @@ async def complete_order(callback_query: types.CallbackQuery):
|
|
| 265 |
cart['completed'] = True
|
| 266 |
cart['date'] = datetime.now().isoformat()
|
| 267 |
save_data(data)
|
|
|
|
|
|
|
| 268 |
|
| 269 |
await bot.send_message(user_id, f"Ваш заказ принят! Для завершения оформите его через WhatsApp:\n{whatsapp_link}")
|
| 270 |
await bot.answer_callback_query(callback_query.id)
|
|
@@ -277,16 +311,24 @@ async def complete_order(callback_query: types.CallbackQuery):
|
|
| 277 |
@dp.message(F.text == "📦 Заказы")
|
| 278 |
async def show_orders(message: types.Message):
|
| 279 |
user_id = message.from_user.id
|
| 280 |
-
user_orders = [o for o in data
|
| 281 |
if not user_orders:
|
| 282 |
await message.answer("У вас нет оформленных заказов.")
|
| 283 |
return
|
| 284 |
response_list = ["Ваши оформленные заказы:\n"]
|
| 285 |
for order in user_orders:
|
| 286 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
total = 0
|
| 288 |
for item in order['items']:
|
| 289 |
-
product = next((p for p in data
|
| 290 |
if product:
|
| 291 |
order_text += f"🏷 {product['name']} - {product['price']} сом x {item['quantity']}\n"
|
| 292 |
total += product['price'] * item['quantity']
|
|
@@ -472,7 +514,7 @@ admin_html = """
|
|
| 472 |
{% for order in completed_orders %}
|
| 473 |
<div class="item">
|
| 474 |
Пользователь: {{ order.user_id }}<br>
|
| 475 |
-
|
| 476 |
Товары:
|
| 477 |
{% for item in order['items'] %}
|
| 478 |
{% set product = products | selectattr('id', 'equalto', item.product_id) | first %}
|
|
@@ -499,7 +541,7 @@ admin_html = """
|
|
| 499 |
{% for order in pending_orders %}
|
| 500 |
<div class="item">
|
| 501 |
Пользователь: {{ order.user_id }}<br>
|
| 502 |
-
Дата создания: {{ order.date }}<br>
|
| 503 |
Товары в корзине:
|
| 504 |
{% for item in order['items'] %}
|
| 505 |
{% set product = products | selectattr('id', 'equalto', item.product_id) | first %}
|
|
@@ -577,7 +619,7 @@ def add_product():
|
|
| 577 |
description = request.form['description']
|
| 578 |
category_id = int(request.form['category_id'])
|
| 579 |
photos = request.files.getlist('photo')
|
| 580 |
-
product_id = max((p['id'] for p in data
|
| 581 |
|
| 582 |
photos_filenames = []
|
| 583 |
if HF_TOKEN_WRITE:
|
|
@@ -621,12 +663,13 @@ def add_product():
|
|
| 621 |
return redirect("/")
|
| 622 |
except Exception as e:
|
| 623 |
logger.error(f"Ошибка при добавлении товара: {e}")
|
| 624 |
-
|
|
|
|
| 625 |
|
| 626 |
@app.route('/delete_product/<int:product_id>', methods=['POST'])
|
| 627 |
def delete_product(product_id):
|
| 628 |
try:
|
| 629 |
-
data['products'] = [p for p in data
|
| 630 |
save_data(data)
|
| 631 |
upload_db_to_hf()
|
| 632 |
update_event.set()
|
|
@@ -639,7 +682,7 @@ def delete_product(product_id):
|
|
| 639 |
def add_category():
|
| 640 |
try:
|
| 641 |
name = request.form['name']
|
| 642 |
-
category_id = max((c['id'] for c in data
|
| 643 |
data['categories'].append({'id': category_id, 'name': name})
|
| 644 |
save_data(data)
|
| 645 |
upload_db_to_hf()
|
|
@@ -647,12 +690,12 @@ def add_category():
|
|
| 647 |
return redirect("/")
|
| 648 |
except Exception as e:
|
| 649 |
logger.error(f"Ошибка при добавлении категории: {e}")
|
| 650 |
-
return
|
| 651 |
|
| 652 |
@app.route('/delete_category/<int:category_id>', methods=['POST'])
|
| 653 |
def delete_category(category_id):
|
| 654 |
try:
|
| 655 |
-
data['categories'] = [c for c in data
|
| 656 |
save_data(data)
|
| 657 |
upload_db_to_hf()
|
| 658 |
update_event.set()
|
|
@@ -675,10 +718,12 @@ async def async_send_broadcast(bot: Bot, user_ids: list[int], text: str, media_u
|
|
| 675 |
await asyncio.sleep(0.1)
|
| 676 |
except Exception as e:
|
| 677 |
failed_count += 1
|
|
|
|
| 678 |
logger.error(f"Не удалось отправить объявление пользователю {user_id}: {e}")
|
| 679 |
await asyncio.sleep(0.1)
|
| 680 |
logger.info(f"Рассылка завершена. Успешно отправлено: {sent_count}, Ошибок: {failed_count}")
|
| 681 |
|
|
|
|
| 682 |
@app.route('/send_announcement', methods=['POST'])
|
| 683 |
def send_announcement():
|
| 684 |
try:
|
|
@@ -689,14 +734,14 @@ def send_announcement():
|
|
| 689 |
if not text:
|
| 690 |
return jsonify({'status': 'error', 'message': 'Текст объявления обязателен'}), 400
|
| 691 |
|
| 692 |
-
#
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
|
|
|
|
|
|
|
|
|
| 696 |
|
| 697 |
-
if asyncio_loop is None:
|
| 698 |
-
logger.error("asyncio_loop не инициализирован после ожидания bot_is_ready.")
|
| 699 |
-
return jsonify({'status': 'error', 'message': 'Внутренняя ошибка сервера: петля событий бота не инициализирована.'}), 500
|
| 700 |
|
| 701 |
if media_file and media_file.filename:
|
| 702 |
if HF_TOKEN_WRITE:
|
|
@@ -729,22 +774,21 @@ def send_announcement():
|
|
| 729 |
|
| 730 |
user_ids_to_send = list(data.get('users', []))
|
| 731 |
if user_ids_to_send:
|
| 732 |
-
# Use call_soon_threadsafe to schedule the async function on the bot's event loop
|
| 733 |
asyncio_loop.call_soon_threadsafe(
|
| 734 |
-
asyncio_loop.create_task,
|
| 735 |
async_send_broadcast(bot, user_ids_to_send, text, media_url)
|
| 736 |
)
|
| 737 |
logger.info(f"Задача рассылки поставлена в очередь для {len(user_ids_to_send)} пользователей.")
|
| 738 |
else:
|
| 739 |
logger.warning("Нет пользователей для отправки объявления.")
|
| 740 |
|
| 741 |
-
# Always redirect back to the admin page after processing
|
| 742 |
return redirect("/")
|
| 743 |
|
| 744 |
except Exception as e:
|
| 745 |
logger.error(f"Ошибка при обработке запроса отправки объявления: {e}")
|
| 746 |
# Redirect on unexpected errors as well
|
| 747 |
-
return redirect("/")
|
|
|
|
| 748 |
|
| 749 |
@app.route('/updates')
|
| 750 |
def sse_updates():
|
|
@@ -770,6 +814,9 @@ def run_flask():
|
|
| 770 |
app.run(host='0.0.0.0', port=7860, debug=True, use_reloader=False)
|
| 771 |
|
| 772 |
if __name__ == '__main__':
|
|
|
|
|
|
|
|
|
|
| 773 |
flask_thread = threading.Thread(target=run_flask, daemon=True)
|
| 774 |
flask_thread.start()
|
| 775 |
logger.info("Flask запущен")
|
|
|
|
| 26 |
|
| 27 |
DATA_FILE = 'datatestoboto.json'
|
| 28 |
|
| 29 |
+
REPO_ID = "Kgshop/bottest" # Changed repository ID
|
| 30 |
HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
|
| 31 |
HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
|
| 32 |
|
|
|
|
| 51 |
return loaded_data
|
| 52 |
except Exception as e:
|
| 53 |
logger.error(f"Ошибка при загрузке данных: {e}")
|
| 54 |
+
# If download fails AND local file doesn't exist or is corrupted, return empty structure
|
| 55 |
+
if not os.path.exists(DATA_FILE) or not isinstance(load_data_from_local(), dict):
|
| 56 |
+
return {'products': [], 'orders': [], 'categories': [], 'users': []}
|
| 57 |
+
else:
|
| 58 |
+
# If download fails but local data is usable, load local data
|
| 59 |
+
logger.warning("Загрузка с HF не удалась, использую локальные данные.")
|
| 60 |
+
return load_data_from_local()
|
| 61 |
+
|
| 62 |
+
def load_data_from_local():
|
| 63 |
+
try:
|
| 64 |
+
with open(DATA_FILE, 'r', encoding='utf-8') as f:
|
| 65 |
+
loaded_data = json.load(f)
|
| 66 |
+
if not (isinstance(loaded_data, dict) and 'products' in loaded_data and 'orders' in loaded_data):
|
| 67 |
+
return {'products': [], 'orders': [], 'categories': [], 'users': []}
|
| 68 |
+
if "categories" not in loaded_data:
|
| 69 |
+
loaded_data["categories"] = []
|
| 70 |
+
if "users" not in loaded_data:
|
| 71 |
+
loaded_data["users"] = []
|
| 72 |
+
for order in loaded_data['orders']:
|
| 73 |
+
if 'completed' not in order:
|
| 74 |
+
order['completed'] = False
|
| 75 |
+
return loaded_data
|
| 76 |
+
except Exception as e:
|
| 77 |
+
logger.error(f"Ошибка при загрузке локальных данных: {e}")
|
| 78 |
+
return {'products': [], 'orders': [], 'categories': [], 'users': []}
|
| 79 |
+
|
| 80 |
|
| 81 |
def save_data(data):
|
| 82 |
try:
|
|
|
|
| 90 |
if not HF_TOKEN_WRITE:
|
| 91 |
logger.warning("HF_TOKEN_WRITE не установлен. Пропуск загрузки на Hugging Face.")
|
| 92 |
return
|
| 93 |
+
if not os.path.exists(DATA_FILE):
|
| 94 |
+
logger.warning(f"Файл данных {DATA_FILE} не найден. Пропуск загрузки на Hugging Face.")
|
| 95 |
+
return
|
| 96 |
+
|
| 97 |
api = HfApi()
|
| 98 |
api.upload_file(
|
| 99 |
path_or_fileobj=DATA_FILE,
|
|
|
|
| 124 |
logger.info("База скачана из Hugging Face")
|
| 125 |
except Exception as e:
|
| 126 |
logger.error(f"Ошибка при скачивании: {e}")
|
| 127 |
+
raise
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
def start_periodic_backup():
|
| 130 |
def backup_loop():
|
|
|
|
| 158 |
@dp.message(Command("start"))
|
| 159 |
async def cmd_start(message: types.Message):
|
| 160 |
user_id = message.from_user.id
|
| 161 |
+
if user_id not in data.get('users', []):
|
| 162 |
data['users'].append(user_id)
|
| 163 |
save_data(data)
|
| 164 |
+
upload_db_to_hf() # Upload after adding a new user
|
| 165 |
logger.info(f"Новый пользователь добавлен: {user_id}")
|
| 166 |
await message.answer("Здравствуйте ! это магазин Routine!. Выберите действие:", reply_markup=get_main_keyboard())
|
| 167 |
|
|
|
|
| 178 |
cat_id = int(callback_query.data.split('_')[1])
|
| 179 |
products_in_cat = [p for p in data['products'] if p.get('category_id') == cat_id]
|
| 180 |
|
| 181 |
+
await bot.answer_callback_query(callback_query.id) # Answer immediately
|
| 182 |
+
|
| 183 |
if not products_in_cat:
|
| 184 |
await bot.send_message(callback_query.from_user.id, "В этой категории нет товаров.")
|
|
|
|
| 185 |
return
|
| 186 |
|
| 187 |
async def send_product_batch(products_batch):
|
|
|
|
| 197 |
media_group.append(InputMediaPhoto(media=photo_url, caption=caption))
|
| 198 |
else:
|
| 199 |
media_group.append(InputMediaPhoto(media=photo_url))
|
| 200 |
+
if media_group: # Send media group only if there are photos
|
| 201 |
+
await bot.send_media_group(chat_id=callback_query.from_user.id, media=media_group)
|
| 202 |
+
# Always send the keyboard separately for reliability, especially with media groups
|
| 203 |
await bot.send_message(callback_query.from_user.id, "Выберите действие:", reply_markup=get_product_keyboard(product['id']))
|
| 204 |
else:
|
| 205 |
await bot.send_message(callback_query.from_user.id, caption, reply_markup=get_product_keyboard(product['id']))
|
| 206 |
except Exception as e:
|
| 207 |
+
logger.error(f"Ошибка при отправке медиа или сообщения для товара {product.get('id')}: {e}")
|
| 208 |
+
# Fallback to sending text if media fails
|
| 209 |
await bot.send_message(callback_query.from_user.id, caption, reply_markup=get_product_keyboard(product['id']))
|
| 210 |
|
| 211 |
+
batch_size = 3 # Adjusted batch size for potentially faster sending
|
| 212 |
for i in range(0, len(products_in_cat), batch_size):
|
| 213 |
batch = products_in_cat[i:i + batch_size]
|
| 214 |
await send_product_batch(batch)
|
| 215 |
+
await asyncio.sleep(0.5) # Increase sleep slightly
|
| 216 |
|
|
|
|
| 217 |
except Exception as e:
|
| 218 |
logger.error(f"Ошибка в show_products_in_category: {e}")
|
| 219 |
+
await bot.send_message(callback_query.from_user.id, "Произошла ошибка при загрузке товаров.")
|
| 220 |
+
|
| 221 |
|
| 222 |
@dp.message(F.text == "🛒 Корзина")
|
| 223 |
async def show_cart(message: types.Message):
|
| 224 |
user_id = message.from_user.id
|
| 225 |
+
cart = next((o for o in data.get('orders', []) if o['user_id'] == user_id and not o.get('completed')), None)
|
| 226 |
if not cart or not cart['items']:
|
| 227 |
await message.answer("Ваша корзина пуста.")
|
| 228 |
return
|
| 229 |
total = 0
|
| 230 |
response = "Ваша корзина:\n"
|
| 231 |
for item in cart['items']:
|
| 232 |
+
product = next((p for p in data.get('products', []) if p['id'] == item['product_id']), None)
|
| 233 |
if product:
|
| 234 |
response += f"🏷 {product['name']} - {product['price']} сом x {item['quantity']}\n"
|
| 235 |
total += product['price'] * item['quantity']
|
|
|
|
| 260 |
parts = callback_query.data.split('_')
|
| 261 |
product_id = int(parts[1])
|
| 262 |
quantity = int(parts[2])
|
| 263 |
+
product = next((p for p in data.get('products', []) if p['id'] == product_id), None)
|
| 264 |
if product:
|
| 265 |
user_id = callback_query.from_user.id
|
| 266 |
+
cart = next((o for o in data.get('orders', []) if o['user_id'] == user_id and not o.get('completed')), None)
|
| 267 |
if not cart:
|
| 268 |
cart = {'user_id': user_id, 'items': [], 'date': datetime.now().isoformat(), 'completed': False}
|
| 269 |
data['orders'].append(cart)
|
| 270 |
cart['items'].append({'product_id': product_id, 'quantity': quantity})
|
| 271 |
save_data(data)
|
| 272 |
+
upload_db_to_hf()
|
| 273 |
await bot.answer_callback_query(callback_query.id, "Товар добавлен в корзину!")
|
| 274 |
else:
|
| 275 |
await bot.answer_callback_query(callback_query.id, "Товар не найден")
|
|
|
|
| 281 |
async def complete_order(callback_query: types.CallbackQuery):
|
| 282 |
try:
|
| 283 |
user_id = int(callback_query.data.split('_')[1])
|
| 284 |
+
cart = next((o for o in data.get('orders', []) if o['user_id'] == user_id and not o.get('completed')), None)
|
| 285 |
if cart and cart['items']:
|
| 286 |
total = 0
|
| 287 |
cart_text = "Привет, я хочу сделать заказ:\n"
|
| 288 |
for item in cart['items']:
|
| 289 |
+
product = next((p for p in data.get('products', []) if p['id'] == item['product_id']), None)
|
| 290 |
if product:
|
| 291 |
cart_text += f"{product['name']} - {product['price']} сом x {item['quantity']}\n"
|
| 292 |
total += product['price'] * item['quantity']
|
|
|
|
| 297 |
cart['completed'] = True
|
| 298 |
cart['date'] = datetime.now().isoformat()
|
| 299 |
save_data(data)
|
| 300 |
+
upload_db_to_hf()
|
| 301 |
+
|
| 302 |
|
| 303 |
await bot.send_message(user_id, f"Ваш заказ принят! Для завершения оформите его через WhatsApp:\n{whatsapp_link}")
|
| 304 |
await bot.answer_callback_query(callback_query.id)
|
|
|
|
| 311 |
@dp.message(F.text == "📦 Заказы")
|
| 312 |
async def show_orders(message: types.Message):
|
| 313 |
user_id = message.from_user.id
|
| 314 |
+
user_orders = [o for o in data.get('orders', []) if o.get('completed') and o['user_id'] == user_id]
|
| 315 |
if not user_orders:
|
| 316 |
await message.answer("У вас нет оформленных заказов.")
|
| 317 |
return
|
| 318 |
response_list = ["Ваши оформленные заказы:\n"]
|
| 319 |
for order in user_orders:
|
| 320 |
+
# Safely handle potential missing 'date' key or invalid format
|
| 321 |
+
date_str = order.get('date', 'Неизвестная дата')
|
| 322 |
+
try:
|
| 323 |
+
date_obj = datetime.fromisoformat(date_str)
|
| 324 |
+
formatted_date = date_obj.strftime('%Y-%m-%d %H:%M')
|
| 325 |
+
except ValueError:
|
| 326 |
+
formatted_date = date_str
|
| 327 |
+
|
| 328 |
+
order_text = f"--- Заказ от {formatted_date} ---\n"
|
| 329 |
total = 0
|
| 330 |
for item in order['items']:
|
| 331 |
+
product = next((p for p in data.get('products', []) if p['id'] == item['product_id']), None)
|
| 332 |
if product:
|
| 333 |
order_text += f"🏷 {product['name']} - {product['price']} сом x {item['quantity']}\n"
|
| 334 |
total += product['price'] * item['quantity']
|
|
|
|
| 514 |
{% for order in completed_orders %}
|
| 515 |
<div class="item">
|
| 516 |
Пользователь: {{ order.user_id }}<br>
|
| 517 |
+
Дата: {{ order.date | default('Неизвестная дата') }}<br>
|
| 518 |
Товары:
|
| 519 |
{% for item in order['items'] %}
|
| 520 |
{% set product = products | selectattr('id', 'equalto', item.product_id) | first %}
|
|
|
|
| 541 |
{% for order in pending_orders %}
|
| 542 |
<div class="item">
|
| 543 |
Пользователь: {{ order.user_id }}<br>
|
| 544 |
+
Дата создания: {{ order.date | default('Неизвестная дата') }}<br>
|
| 545 |
Товары в корзине:
|
| 546 |
{% for item in order['items'] %}
|
| 547 |
{% set product = products | selectattr('id', 'equalto', item.product_id) | first %}
|
|
|
|
| 619 |
description = request.form['description']
|
| 620 |
category_id = int(request.form['category_id'])
|
| 621 |
photos = request.files.getlist('photo')
|
| 622 |
+
product_id = max((p['id'] for p in data.get('products', [])), default=0) + 1
|
| 623 |
|
| 624 |
photos_filenames = []
|
| 625 |
if HF_TOKEN_WRITE:
|
|
|
|
| 663 |
return redirect("/")
|
| 664 |
except Exception as e:
|
| 665 |
logger.error(f"Ошибка при добавлении товара: {e}")
|
| 666 |
+
# Redirect on error instead of jsonify for better admin panel flow
|
| 667 |
+
return redirect("/")
|
| 668 |
|
| 669 |
@app.route('/delete_product/<int:product_id>', methods=['POST'])
|
| 670 |
def delete_product(product_id):
|
| 671 |
try:
|
| 672 |
+
data['products'] = [p for p in data.get('products', []) if p['id'] != product_id]
|
| 673 |
save_data(data)
|
| 674 |
upload_db_to_hf()
|
| 675 |
update_event.set()
|
|
|
|
| 682 |
def add_category():
|
| 683 |
try:
|
| 684 |
name = request.form['name']
|
| 685 |
+
category_id = max((c['id'] for c in data.get('categories', [])), default=0) + 1
|
| 686 |
data['categories'].append({'id': category_id, 'name': name})
|
| 687 |
save_data(data)
|
| 688 |
upload_db_to_hf()
|
|
|
|
| 690 |
return redirect("/")
|
| 691 |
except Exception as e:
|
| 692 |
logger.error(f"Ошибка при добавлении категории: {e}")
|
| 693 |
+
return redirect("/")
|
| 694 |
|
| 695 |
@app.route('/delete_category/<int:category_id>', methods=['POST'])
|
| 696 |
def delete_category(category_id):
|
| 697 |
try:
|
| 698 |
+
data['categories'] = [c for c in data.get('categories', []) if c['id'] != category_id]
|
| 699 |
save_data(data)
|
| 700 |
upload_db_to_hf()
|
| 701 |
update_event.set()
|
|
|
|
| 718 |
await asyncio.sleep(0.1)
|
| 719 |
except Exception as e:
|
| 720 |
failed_count += 1
|
| 721 |
+
# Log specific error for the user
|
| 722 |
logger.error(f"Не удалось отправить объявление пользователю {user_id}: {e}")
|
| 723 |
await asyncio.sleep(0.1)
|
| 724 |
logger.info(f"Рассылка завершена. Успешно отправлено: {sent_count}, Ошибок: {failed_count}")
|
| 725 |
|
| 726 |
+
|
| 727 |
@app.route('/send_announcement', methods=['POST'])
|
| 728 |
def send_announcement():
|
| 729 |
try:
|
|
|
|
| 734 |
if not text:
|
| 735 |
return jsonify({'status': 'error', 'message': 'Текст объявления обязателен'}), 400
|
| 736 |
|
| 737 |
+
# Check if the bot's asyncio loop is available and the bot is ready
|
| 738 |
+
# Increased timeout to 20 seconds as startup might take longer
|
| 739 |
+
if not bot_is_ready.wait(timeout=20) or asyncio_loop is None:
|
| 740 |
+
logger.error("Бот не стал готов в течение 20 секунд или asyncio_loop не установлен.")
|
| 741 |
+
# Redirect instead of jsonify for better admin panel UX on this specific error
|
| 742 |
+
# return jsonify({'status': 'error', 'message': 'Бот еще не готов. Попробуйте позже.'}), 503
|
| 743 |
+
return redirect("/?error=bot_not_ready") # Redirect and indicate error
|
| 744 |
|
|
|
|
|
|
|
|
|
|
| 745 |
|
| 746 |
if media_file and media_file.filename:
|
| 747 |
if HF_TOKEN_WRITE:
|
|
|
|
| 774 |
|
| 775 |
user_ids_to_send = list(data.get('users', []))
|
| 776 |
if user_ids_to_send:
|
|
|
|
| 777 |
asyncio_loop.call_soon_threadsafe(
|
| 778 |
+
asyncio_loop.create_task,
|
| 779 |
async_send_broadcast(bot, user_ids_to_send, text, media_url)
|
| 780 |
)
|
| 781 |
logger.info(f"Задача рассылки поставлена в очередь для {len(user_ids_to_send)} пользователей.")
|
| 782 |
else:
|
| 783 |
logger.warning("Нет пользователей для отправки объявления.")
|
| 784 |
|
|
|
|
| 785 |
return redirect("/")
|
| 786 |
|
| 787 |
except Exception as e:
|
| 788 |
logger.error(f"Ошибка при обработке запроса отправки объявления: {e}")
|
| 789 |
# Redirect on unexpected errors as well
|
| 790 |
+
return redirect("/?error=send_failed") # Redirect and indicate error
|
| 791 |
+
|
| 792 |
|
| 793 |
@app.route('/updates')
|
| 794 |
def sse_updates():
|
|
|
|
| 814 |
app.run(host='0.0.0.0', port=7860, debug=True, use_reloader=False)
|
| 815 |
|
| 816 |
if __name__ == '__main__':
|
| 817 |
+
# Load data initially
|
| 818 |
+
data = load_data() # Ensure data is loaded before starting Flask/bot
|
| 819 |
+
|
| 820 |
flask_thread = threading.Thread(target=run_flask, daemon=True)
|
| 821 |
flask_thread.start()
|
| 822 |
logger.info("Flask запущен")
|