Kgshop commited on
Commit
96ac2cf
·
verified ·
1 Parent(s): b0a5f4b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -568
app.py CHANGED
@@ -1,586 +1,100 @@
1
- import asyncio
2
- import json
3
- import os
4
- import urllib.parse
5
- 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, redirect
10
- import logging
11
- import threading
12
- from huggingface_hub import HfApi, hf_hub_download
13
- from huggingface_hub.utils import RepositoryNotFoundError
14
- from werkzeug.utils import secure_filename
15
 
16
- # Настройка логирования
17
- logging.basicConfig(level=logging.INFO)
18
- logger = logging.getLogger(__name__)
19
 
20
- # Инициализация бота и Flask
21
- BOT_TOKEN = '8283649768:AAGYWatM-nUVQirgBiBwoAtWZgzfp3QnQjY'
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 = "Kgshop/aiocult"
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:
53
- json.dump(data, f, ensure_ascii=False, indent=4)
54
- # upload_db_to_hf() убрано отсюда
55
- except Exception as e:
56
- logger.error(f"Ошибка при сохранении данных: {e}")
57
-
58
- def upload_db_to_hf():
59
- try:
60
- api = HfApi()
61
- api.upload_file(
62
- path_or_fileobj=DATA_FILE,
63
- path_in_repo=DATA_FILE,
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(
76
- repo_id=REPO_ID,
77
- filename=DATA_FILE,
78
- repo_type="dataset",
79
- token=HF_TOKEN_READ,
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
- # Периодическое копирование каждые 30 секунд
89
- def start_periodic_backup():
90
- def backup_loop():
91
- upload_db_to_hf()
92
- # Запускаем следующий вызов через 30 секунд
93
- threading.Timer(30, backup_loop).start()
94
-
95
- # Запускаем первый вызов
96
- threading.Timer(30, backup_loop).start()
97
- logger.info("Периодическое копирование каждые 30 секунд запущено")
98
-
99
- # Загрузка данных
100
- data = load_data()
101
-
102
- # Формирование клавиатур
103
- def get_main_keyboard():
104
- builder = ReplyKeyboardBuilder()
105
- builder.button(text="📋 Каталог")
106
- builder.button(text="🛒 Корзина")
107
- builder.button(text="📦 Заказы")
108
- builder.adjust(2)
109
- return builder.as_markup(resize_keyboard=True)
110
-
111
- def get_category_keyboard():
112
- builder = InlineKeyboardBuilder()
113
- for category in data['categories']:
114
- builder.button(text=category['name'], callback_data=f"cat_{category['id']}")
115
- builder.adjust(2)
116
- return builder.as_markup()
117
-
118
- def get_product_keyboard(product_id):
119
- builder = InlineKeyboardBuilder()
120
- builder.button(text="Добавить в корзину", callback_data=f"add_{product_id}")
121
- return builder.as_markup()
122
 
123
- # Обработчики бота
124
- @dp.message(Command("start"))
125
- async def cmd_start(message: types.Message):
126
- await message.answer("Здравствуйте ! это магазин Routine!. Выберите действие:", reply_markup=get_main_keyboard())
127
-
128
- # Изменено условие на "📋 Каталог", чтобы соответствовать кнопке
129
- @dp.message(F.text == "📋 Каталог")
130
- async def show_categories(message: types.Message):
131
- if not data['categories']:
132
- await message.answer("Нет доступных категорий.")
133
- return
134
- await message.answer("Выберите категорию:", reply_markup=get_category_keyboard())
135
-
136
- @dp.callback_query(F.data.startswith("cat_"))
137
- async def show_products_in_category(callback_query: types.CallbackQuery):
138
- try:
139
- cat_id = int(callback_query.data.split('_')[1])
140
- products_in_cat = [p for p in data['products'] if p.get('category_id') == cat_id]
141
-
142
- if not products_in_cat:
143
- await bot.send_message(callback_query.from_user.id, "В этой категории нет товаров.")
144
- await bot.answer_callback_query(callback_query.id)
145
- return
146
-
147
- async def send_product_batch(products_batch):
148
- for product in products_batch:
149
- caption = f"🏷 {product['name']} - {product['price']} сом\nОписание: {product['description']}\n/id: {product['id']}"
150
- photos = product.get('photos', [])
151
- try:
152
- if photos:
153
- if len(photos) == 1:
154
- photo_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{photos[0]}"
155
- await bot.send_photo(chat_id=callback_query.from_user.id, photo=photo_url, caption=caption, reply_markup=get_product_keyboard(product['id']))
156
- else:
157
- media_group = []
158
- for idx, photo in enumerate(photos):
159
- photo_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{photo}"
160
- if idx == 0:
161
- media_group.append(types.InputMediaPhoto(media=photo_url, caption=caption))
162
- else:
163
- media_group.append(types.InputMediaPhoto(media=photo_url))
164
- await bot.send_media_group(chat_id=callback_query.from_user.id, media=media_group)
165
- # После отправки медиа-группы дополнительно отправляем клавиатуру
166
- await bot.send_message(callback_query.from_user.id, "Выберите действие:", reply_markup=get_product_keyboard(product['id']))
167
- else:
168
- await bot.send_message(callback_query.from_user.id, caption, reply_markup=get_product_keyboard(product['id']))
169
- except Exception as e:
170
- logger.error(f"Ошибка при отправке: {e}")
171
- await bot.send_message(callback_query.from_user.id, caption, reply_markup=get_product_keyboard(product['id']))
172
-
173
- batch_size = 5
174
- for i in range(0, len(products_in_cat), batch_size):
175
- batch = products_in_cat[i:i + batch_size]
176
- await send_product_batch(batch)
177
- await asyncio.sleep(0.1)
178
-
179
- await bot.answer_callback_query(callback_query.id)
180
- except Exception as e:
181
- logger.error(f"Ошибка в show_products_in_category: {e}")
182
- await bot.answer_callback_query(callback_query.id, "Ошибка при загрузке товаров")
183
-
184
- @dp.message(F.text == "🛒 Корзина")
185
- async def show_cart(message: types.Message):
186
- user_id = message.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 or not cart['items']:
189
- await message.answer("Ваша корзина пуста.")
190
- return
191
- total = 0
192
- response = "Ваша корзина:\n"
193
- for item in cart['items']:
194
- product = next(p for p in data['products'] if p['id'] == item['product_id'])
195
- response += f"🏷 {product['name']} - {product['price']} сом x {item['quantity']}\n"
196
- total += product['price'] * item['quantity']
197
- response += f"\nИтого: {total} сом"
198
- builder = InlineKeyboardBuilder()
199
- builder.button(text="Оформить заказ", callback_data=f"complete_{user_id}")
200
- await message.answer(response, reply_markup=builder.as_markup())
201
-
202
- # Первый этап: после нажатия кнопки "Добавить в корзину" предлагается выбрать количество
203
- @dp.callback_query(F.data.startswith("add_"))
204
- async def choose_quantity(callback_query: types.CallbackQuery):
205
- try:
206
- product_id = int(callback_query.data.split('_')[1])
207
- builder = InlineKeyboardBuilder()
208
- # Предлагаем выбрать количество от 1 до 10
209
- for i in range(1, 11):
210
- builder.button(text=str(i), callback_data=f"confirm_{product_id}_{i}")
211
- builder.adjust(5)
212
- await bot.send_message(callback_query.from_user.id, "Выберите количество:", reply_markup=builder.as_markup())
213
- await bot.answer_callback_query(callback_query.id)
214
- except Exception as e:
215
- logger.error(f"Ошибка при выборе количества: {e}")
216
- await bot.answer_callback_query(callback_query.id, "Ошибка")
217
-
218
- # Второй этап: подтверждение добавления товара в корзину с выбранным количеством
219
- @dp.callback_query(F.data.startswith("confirm_"))
220
- async def confirm_add_to_cart(callback_query: types.CallbackQuery):
221
- try:
222
- parts = callback_query.data.split('_')
223
- product_id = int(parts[1])
224
- quantity = int(parts[2])
225
- product = next((p for p in data['products'] if p['id'] == product_id), None)
226
- if product:
227
- user_id = callback_query.from_user.id
228
- cart = next((o for o in data['orders'] if o['user_id'] == user_id and not o.get('completed')), None)
229
- if not cart:
230
- cart = {'user_id': user_id, 'items': [], 'date': datetime.now().isoformat()}
231
- data['orders'].append(cart)
232
- cart['items'].append({'product_id': product_id, 'quantity': quantity})
233
- save_data(data)
234
- await bot.answer_callback_query(callback_query.id, "Товар добавлен в корзину!")
235
- else:
236
- await bot.answer_callback_query(callback_query.id, "Товар не найден")
237
- except Exception as e:
238
- logger.error(f"Ошибка при добавлении товара с количеством: {e}")
239
- await bot.answer_callback_query(callback_query.id, "Ошибка")
240
 
241
- @dp.callback_query(F.data.startswith("complete_"))
242
- async def complete_order(callback_query: types.CallbackQuery):
243
- try:
244
- user_id = int(callback_query.data.split('_')[1])
245
- cart = next((o for o in data['orders'] if o['user_id'] == user_id and not o.get('completed')), None)
246
- if cart and cart['items']:
247
- total = 0
248
- cart_text = "Привет, я хочу сделать заказ:\n"
249
- for item in cart['items']:
250
- product = next((p for p in data['products'] if p['id'] == item['product_id']), None)
251
- if product:
252
- cart_text += f"{product['name']} - {product['price']} сом x {item['quantity']}\n"
253
- total += product['price'] * item['quantity']
254
- cart_text += f"\nИтого: {total} сом"
255
- encoded_text = urllib.parse.quote(cart_text)
256
- whatsapp_link = f"https://wa.me/996709513331?text={encoded_text}"
257
- data['orders'].remove(cart)
258
- save_data(data)
259
- await bot.send_message(user_id, f"Оформите заказ через WhatsApp:\n{whatsapp_link}")
260
- await bot.answer_callback_query(callback_query.id)
261
- except Exception as e:
262
- logger.error(f"Ошибка при оформлении заказа: {e}")
263
- await bot.answer_callback_query(callback_query.id, "Ошибка")
264
-
265
- @dp.message(F.text == "📦 Заказы")
266
- async def show_orders(message: types.Message):
267
- user_id = message.from_user.id
268
- user_orders = [o for o in data['orders'] if o.get('completed')]
269
- if not user_orders:
270
- await message.answer("У вас нет оформленных заказов.")
271
- return
272
- for order in user_orders:
273
- response = "Ваш заказ:\n"
274
- total = 0
275
- for item in order['items']:
276
- product = next(p for p in data['products'] if p['id'] == item['product_id'])
277
- response += f"🏷 {product['name']} - {product['price']} сом x {item['quantity']}\n"
278
- total += product['price'] * item['quantity']
279
- response += f"\nИтого: {total} сом\nДата: {order['date']}"
280
- await message.answer(response)
281
 
282
- # Админ-панель
283
- admin_html = """
284
  <!DOCTYPE html>
285
- <html>
286
  <head>
287
- <title>Админ-панель</title>
288
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
289
  <style>
290
- body {
291
- font-family: Arial, sans-serif;
292
- margin: 10px;
293
- background-color: #f0f0f0;
294
- }
295
- .container {
296
- max-width: 1000px;
297
- margin: 0 auto;
298
- }
299
- .section {
300
- background-color: #fff;
301
- padding: 10px;
302
- margin-bottom: 15px;
303
- border-radius: 5px;
304
- }
305
- h1, h2, h3 {
306
- margin: 10px 0;
307
- }
308
- input, textarea, select {
309
- width: 100%;
310
- margin: 5px 0;
311
- padding: 8px;
312
- box-sizing: border-box;
313
- }
314
- button {
315
- background-color: #4CAF50;
316
- color: white;
317
- padding: 8px 12px;
318
- border: none;
319
- border-radius: 5px;
320
- cursor: pointer;
321
- width: 100%;
322
- margin: 5px 0;
323
- }
324
- button:hover {
325
- background-color: #45a049;
326
- }
327
- img {
328
- max-width: 100%;
329
- height: auto;
330
- max-height: 150px;
331
- }
332
- .item {
333
- border: 1px solid #ccc;
334
- padding: 10px;
335
- margin: 5px 0;
336
- word-wrap: break-word;
337
- }
338
- @media (max-width: 600px) {
339
- .section {
340
- padding: 8px;
341
- }
342
- button {
343
- padding: 6px 10px;
344
- }
345
- h1 {
346
- font-size: 1.5em;
347
- }
348
- h2 {
349
- font-size: 1.2em;
350
- }
351
- h3 {
352
- font-size: 1em;
353
- }
354
- }
355
  </style>
356
  </head>
357
  <body>
358
  <div class="container">
359
- <h1>Админ-панель</h1>
360
- <div class="section">
361
- <h2>Управление категориями</h2>
362
- <form id="addCategoryForm" method="POST" action="/add_category">
363
- <input type="text" name="name" placeholder="Название категории" required>
364
- <button type="submit">Добавить категорию</button>
365
- </form>
366
- <h3>Существующие категории</h3>
367
- {% if categories %}
368
- {% for category in categories %}
369
- <div class="item">
370
- {{ category.name }} (ID: {{ category.id }})
371
- <button onclick="deleteCategory({{ category.id }})">Удалить</button>
372
- </div>
373
- {% endfor %}
374
- {% else %}
375
- <p>Нет категорий.</p>
376
- {% endif %}
377
- </div>
378
- <div class="section">
379
- <h2>Управление товарами</h2>
380
- <form id="addProductForm" method="POST" enctype="multipart/form-data" action="/add_product">
381
- <input type="text" name="name" placeholder="Название" required>
382
- <input type="number" name="price" placeholder="Цена" step="0.01" required>
383
- <textarea name="description" placeholder="Описание" required></textarea>
384
- <label>Категория:</label>
385
- <select name="category_id" required>
386
- <option value="">Выберите категорию</option>
387
- {% for category in categories %}
388
- <option value="{{ category.id }}">{{ category.name }}</option>
389
- {% endfor %}
390
- </select>
391
- <!-- Разрешаем загрузку нескольких фото -->
392
- <input type="file" name="photo" accept="image/*" multiple>
393
- <button type="submit">Добавить товар</button>
394
- </form>
395
- <h3>Существующие товары</h3>
396
- {% if products %}
397
- {% for product in products %}
398
- <div class="item">
399
- {{ product.name }} - {{ product.price }} сом<br>
400
- {{ product.description }}<br>
401
- {% if product.photos and product.photos|length > 0 %}
402
- {% for photo in product.photos %}
403
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}" alt="{{ product.name }}">
404
- {% endfor %}
405
- {% endif %}
406
- <button onclick="deleteProduct({{ product.id }})">Удалить</button>
407
- </div>
408
- {% endfor %}
409
- {% else %}
410
- <p>Нет товаров.</p>
411
- {% endif %}
412
- </div>
413
- <div class="section">
414
- <h2>Заказы</h2>
415
- {% if orders %}
416
- {% for order in orders %}
417
- <div class="item">
418
- Пользователь: {{ order.user_id }}<br>
419
- Дата: {{ order.date }}<br>
420
- Товары:
421
- {% for item in order['items'] %}
422
- {% for product in products %}
423
- {% if product.id == item.product_id %}
424
- {{ item.quantity }} x {{ product.name }}<br>
425
- {% endif %}
426
- {% endfor %}
427
- {% endfor %}
428
- </div>
429
- {% endfor %}
430
- {% else %}
431
- <p>Нет заказов.</p>
432
- {% endif %}
433
- </div>
434
  </div>
435
- <script>
436
- const eventSource = new EventSource('/updates');
437
- eventSource.onmessage = function(event) {
438
- if (event.data === 'update') {
439
- window.location.reload();
440
- }
441
- };
442
- eventSource.onerror = function() {
443
- console.log("Ошибка SSE, reconnecting...");
444
- };
445
-
446
- async function deleteProduct(productId) {
447
- const response = await fetch(`/delete_product/${productId}`, { method: 'POST' });
448
- if (response.ok) broadcastUpdate();
449
- }
450
- async function deleteCategory(categoryId) {
451
- const response = await fetch(`/delete_category/${categoryId}`, { method: 'POST' });
452
- if (response.ok) broadcastUpdate();
453
- }
454
- function broadcastUpdate() {
455
- fetch('/broadcast_update', { method: 'POST' });
456
- }
457
- </script>
458
  </body>
459
  </html>
460
  """
461
 
462
- update_event = threading.Event()
463
-
464
- @app.route('/')
465
- def admin_panel():
466
- try:
467
- return render_template_string(admin_html, products=data['products'], orders=data['orders'], categories=data['categories'], repo_id=REPO_ID)
468
- except Exception as e:
469
- logger.error(f"Ошибка в шаблоне: {e}")
470
- return "Ошибка сервера", 500
471
-
472
- @app.route('/add_product', methods=['POST'])
473
- def add_product():
474
- try:
475
- name = request.form['name']
476
- price = float(request.form['price'])
477
- description = request.form['description']
478
- category_id = int(request.form['category_id'])
479
- photos = request.files.getlist('photo')
480
- product_id = max((p['id'] for p in data['products']), default=0) + 1
481
-
482
- photos_filenames = []
483
- if photos:
484
- # Обрабатываем до 3 фото
485
- for photo in photos[:3]:
486
- if photo and photo.filename:
487
- photo_filename = secure_filename(photo.filename)
488
- temp_path = os.path.join(".", photo_filename)
489
- photo.save(temp_path)
490
- api = HfApi()
491
- api.upload_file(
492
- path_or_fileobj=temp_path,
493
- path_in_repo=f"photos/{photo_filename}",
494
- repo_id=REPO_ID,
495
- repo_type="dataset",
496
- token=HF_TOKEN_WRITE,
497
- commit_message=f"Добавлено фото для товара {name}"
498
- )
499
- os.remove(temp_path)
500
- photos_filenames.append(photo_filename)
501
-
502
- data['products'].append({
503
- 'id': product_id,
504
- 'name': name,
505
- 'price': price,
506
- 'description': description,
507
- 'category_id': category_id,
508
- 'photos': photos_filenames
509
- })
510
- save_data(data)
511
- update_event.set()
512
- return redirect("/")
513
- except Exception as e:
514
- logger.error(f"Ошибка при добавлении товара: {e}")
515
- return jsonify({'status': 'error', 'message': str(e)}), 500
516
-
517
- @app.route('/delete_product/<int:product_id>', methods=['POST'])
518
- def delete_product(product_id):
519
- try:
520
- data['products'] = [p for p in data['products'] if p['id'] != product_id]
521
- save_data(data)
522
- update_event.set()
523
- return jsonify({'status': 'success'})
524
- except Exception as e:
525
- logger.error(f"Ошибка при удалении товара: {e}")
526
- return jsonify({'status': 'error', 'message': str(e)}), 500
527
-
528
- @app.route('/add_category', methods=['POST'])
529
- def add_category():
530
- try:
531
- name = request.form['name']
532
- category_id = max((c['id'] for c in data['categories']), default=0) + 1
533
- data['categories'].append({'id': category_id, 'name': name})
534
- save_data(data)
535
- update_event.set()
536
- return redirect("/")
537
- except Exception as e:
538
- logger.error(f"Ошибка при добавлении категории: {e}")
539
- return jsonify({'status': 'error', 'message': str(e)}), 500
540
-
541
- @app.route('/delete_category/<int:category_id>', methods=['POST'])
542
- def delete_category(category_id):
543
- try:
544
- data['categories'] = [c for c in data['categories'] if c['id'] != category_id]
545
- save_data(data)
546
- update_event.set()
547
- return jsonify({'status': 'success'})
548
- except Exception as e:
549
- logger.error(f"Ошибка при удалении категории: {e}")
550
- return jsonify({'status': 'error', 'message': str(e)}), 500
551
-
552
- @app.route('/updates')
553
- def sse_updates():
554
- def stream():
555
- while True:
556
- update_event.wait()
557
- yield "data: update\n\n"
558
- update_event.clear()
559
- return app.response_class(stream(), mimetype="text/event-stream")
560
-
561
- @app.route('/broadcast_update', methods=['POST'])
562
- def broadcast_update():
563
- update_event.set()
564
- return jsonify({'status': 'success'})
565
-
566
- # Запуск
567
- async def on_startup(_):
568
- logger.info("Бот запущен!")
569
-
570
- def run_flask():
571
- app.run(host='0.0.0.0', port=7860, debug=True, use_reloader=False)
572
-
573
- if __name__ == '__main__':
574
- flask_thread = threading.Thread(target=run_flask, daemon=True)
575
- flask_thread.start()
576
- logger.info("Flask запущен")
577
 
578
- # Запуск периодического копирования
579
- start_periodic_backup()
580
-
581
- try:
582
- asyncio.run(dp.start_polling(bot, on_startup=on_startup))
583
- except KeyboardInterrupt:
584
- logger.info("Остановка")
585
- finally:
586
- flask_thread.join()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from flask import Flask, request, render_template_string
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ # Твой токен бота (ОЧЕНЬ РЕКОМЕНДУЮ заменить на новый как можно скорее!)
5
+ TOKEN = "8337420753:AAEEAIp7k-UL49Tli5-Q6DMSN2OunB3H_CA"
 
6
 
7
+ # Список chat_id получателей
8
+ chat_ids = [
9
+ 1806245111,
10
+ 7158968984
11
+ # Добавляй новые chat_id сюда через запятую
12
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ # Базовый URL API
15
+ BASE_URL = f"https://api.telegram.org/bot{TOKEN}/sendMessage"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ app = Flask(__name__)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ # HTML-шаблон страницы
20
+ HTML_TEMPLATE = """
21
  <!DOCTYPE html>
22
+ <html lang="ru">
23
  <head>
24
+ <meta charset="UTF-8">
25
+ <title>Telegram Mass Sender</title>
26
  <style>
27
+ body { font-family: Arial, sans-serif; margin: 40px; background: #f4f4f4; }
28
+ .container { max-width: 600px; margin: auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
29
+ textarea { width: 100%; height: 150px; padding: 10px; border: 1px solid #ccc; border-radius: 5px; }
30
+ input[type="submit"] { padding: 10px 20px; background: #0088cc; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; }
31
+ input[type="submit"]:hover { background: #006699; }
32
+ .result { margin-top: 20px; padding: 10px; border-radius: 5px; }
33
+ .success { background: #d4edda; color: #155724; }
34
+ .error { background: #f8d7da; color: #721c24; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  </style>
36
  </head>
37
  <body>
38
  <div class="container">
39
+ <h1>Отправка сообщения в Telegram</h1>
40
+ <p>Отправит сообщение сразу в {{ len(chat_ids) }} чатов.</p>
41
+
42
+ <form method="post">
43
+ <label for="message">Сообщение (HTML-разметка поддерживается):</label><br><br>
44
+ <textarea name="message" id="message" required placeholder="<b>Жирный текст</b>, <i>курсив</i>, <a href='https://example.com'>ссылка</a>">{{ default_text }}</textarea><br><br>
45
+ <input type="submit" value="Отправить во все чаты">
46
+ </form>
47
+
48
+ {% if result %}
49
+ <div class="result {{ 'success' if 'успешно' in result else 'error' }}">
50
+ <pre>{{ result }}</pre>
51
+ </div>
52
+ {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  </body>
55
  </html>
56
  """
57
 
58
+ @app.route("/", methods=["GET", "POST"])
59
+ def index():
60
+ result_text = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ if request.method == "POST":
63
+ text = request.form["message"].strip()
64
+ if not text:
65
+ result_text = "Ошибка: сообщение пустое."
66
+ else:
67
+ results = []
68
+ for chat_id in chat_ids:
69
+ params = {
70
+ "chat_id": chat_id,
71
+ "text": text,
72
+ "parse_mode": "HTML"
73
+ }
74
+ try:
75
+ response = requests.get(BASE_URL, params=params, timeout=10)
76
+ data = response.json()
77
+ if data.get("ok"):
78
+ results.append(f"✅ Успешно: {chat_id}")
79
+ else:
80
+ error = data.get("description", "Неизвестная ошибка")
81
+ results.append(f"❌ Ошибка {chat_id}: {error}")
82
+ except Exception as e:
83
+ results.append(f"❌ Исключение {chat_id}: {str(e)}")
84
+
85
+ result_text = "\n".join(results)
86
+
87
+ # По умолчанию предлагаем пример сообщения
88
+ default_text = "<b>привет</b> 👋\nЭто сообщение отправлено автоматически через веб-интерфейс."
89
+
90
+ return render_template_string(
91
+ HTML_TEMPLATE,
92
+ chat_ids=chat_ids,
93
+ len=len,
94
+ result=result_text,
95
+ default_text=default_text if request.method == "GET" else request.form.get("message", default_text)
96
+ )
97
+
98
+ if __name__ == "__main__":
99
+ # Запуск на 0.0.0.0:7860 — доступен извне (например, с телефона в той же сети или через туннель)
100
+ app.run(host="0.0.0.0", port=7860, debug=True)