diff --git a/web/app.py b/web/app.py index 98d136e..4db8a0c 100644 --- a/web/app.py +++ b/web/app.py @@ -380,38 +380,89 @@ async def broadcast_page(request: Request, username: str = Depends(get_current_a }) +@app.get("/broadcast", response_class=HTMLResponse) +async def broadcast_page(request: Request, username: str = Depends(get_current_admin)): + """Страница рассылок""" + return templates.TemplateResponse("broadcast.html", { + "request": request, "username": username + }) + + @app.post("/api/broadcast/send") -async def api_send_broadcast(request: Request, username: str = Depends(get_current_admin)): +async def api_send_broadcast( + text: str = Form(...), + photo: UploadFile = File(None), + username: str = Depends(get_current_admin) +): """Отправить рассылку""" - import requests - data = await request.json() - message_text = data.get('text', '') + import aiohttp + from pathlib import Path - # Отправляем только в чат дома (через бота) - url = f"https://api.telegram.org/bot{config.BOT_TOKEN}/sendMessage" + photo_id = None - try: - # Используем requests с прокси если настроен - proxies = None - if config.USE_PROXY: - proxy_url = config.get_proxy_url() - if proxy_url: - proxies = {'http': proxy_url, 'https': proxy_url} + # Если есть картинка — загружаем + if photo and photo.filename: + # Сохраняем временно + temp_path = Path("data") / f"broadcast_{photo.filename}" + with open(temp_path, "wb") as f: + content = await photo.read() + f.write(content) - response = requests.post(url, json={ - 'chat_id': config.ADMIN_CHAT_ID, - 'text': f"📢 Объявление от администрации\n\n{message_text}", - 'parse_mode': 'HTML' - }, timeout=30, proxies=proxies) - - result = response.json() - if result.get('ok'): - return JSONResponse({"success": True, "message": "Объявление отправлено в чат дома!"}) - else: - return JSONResponse({"success": False, "error": str(result)}) - except Exception as e: - logger.error(f"Ошибка отправки рассылки: {e}") - return JSONResponse({"success": False, "error": str(e)}, status_code=500) + # Загружаем в Telegram + try: + async with aiohttp.ClientSession() as session: + # Отправляем фото чтобы получить file_id + url = f"https://api.telegram.org/bot{config.BOT_TOKEN}/sendPhoto" + data = aiohttp.FormData() + data.add_field('chat_id', config.ADMIN_USER_ID) + data.add_field('photo', open(temp_path, 'rb'), filename=photo.filename) + data.add_field('caption', 'Test') + + async with session.post(url, data=data) as resp: + result = await resp.json() + if result.get('ok'): + photo_id = result['result']['photo'][-1]['file_id'] + except Exception as e: + logger.error(f"Ошибка загрузки фото: {e}") + + # Рассылаем + async with AsyncSessionLocal() as session: + stmt = select(User).where(User.verified == True) + result = await session.execute(stmt) + users = list(result.scalars().all()) + + success_count = 0 + error_count = 0 + + async with aiohttp.ClientSession() as http_session: + for user in users: + try: + url = f"https://api.telegram.org/bot{config.BOT_TOKEN}/" + if photo_id: + url += "sendPhoto" + data = aiohttp.FormData() + data.add_field('chat_id', user.user_id) + data.add_field('photo', photo_id) + data.add_field('caption', f"📢 Объявление от администрации\n\n{text}") + async with http_session.post(url, data=data, timeout=30) as resp: + if resp.status == 200: + success_count += 1 + else: + url += "sendMessage" + data = aiohttp.FormData() + data.add_field('chat_id', user.user_id) + data.add_field('text', f"📢 Объявление от администрации\n\n{text}") + data.add_field('parse_mode', 'HTML') + async with http_session.post(url, data=data, timeout=30) as resp: + if resp.status == 200: + success_count += 1 + except Exception as e: + error_count += 1 + + return JSONResponse({ + "success": True, + "message": f"Отправлено: {success_count}, Ошибок: {error_count}" + }) # ============================================================================ diff --git a/web/templates/broadcast.html b/web/templates/broadcast.html index 0082d1d..1ba1cd0 100644 --- a/web/templates/broadcast.html +++ b/web/templates/broadcast.html @@ -10,19 +10,22 @@ .sidebar .nav-link { color: rgba(255,255,255,0.8); padding: 1rem; } .sidebar .nav-link:hover, .sidebar .nav-link.active { color: white; background-color: rgba(255,255,255,0.1); } .content { padding: 2rem; } + #preview { max-width: 400px; margin-top: 1rem; display: none; } + #preview img { max-width: 100%; border-radius: 8px; }