|
|
import os |
|
|
import asyncio |
|
|
import threading |
|
|
import uuid |
|
|
from flask import Flask, request, render_template_string, redirect, url_for |
|
|
from aiogram import Bot, Dispatcher, types |
|
|
from aiogram.types import InputFile |
|
|
from aiogram.utils import executor |
|
|
from aiogram.contrib.fsm_storage.memory import MemoryStorage |
|
|
|
|
|
|
|
|
BOT_TOKEN = "7835463659:AAGNePbelZIAOeaglyQi1qulOqnjs4BGQn4" |
|
|
FLASK_HOST = "0.0.0.0" |
|
|
FLASK_PORT = 7860 |
|
|
|
|
|
|
|
|
|
|
|
subscribed_users = set() |
|
|
|
|
|
TEMP_UPLOAD_FOLDER = 'temp_uploads' |
|
|
os.makedirs(TEMP_UPLOAD_FOLDER, exist_ok=True) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loop = asyncio.get_event_loop() |
|
|
bot = Bot(token=BOT_TOKEN) |
|
|
storage = MemoryStorage() |
|
|
dp = Dispatcher(bot, storage=storage, loop=loop) |
|
|
|
|
|
@dp.message_handler(commands=['start']) |
|
|
async def process_start_command(message: types.Message): |
|
|
""" |
|
|
Handler для команды /start. |
|
|
Добавляет пользователя в список подписанных и отправляет приветственное сообщение. |
|
|
""" |
|
|
chat_id = message.chat.id |
|
|
if chat_id not in subscribed_users: |
|
|
subscribed_users.add(chat_id) |
|
|
print(f"User {chat_id} subscribed.") |
|
|
await message.reply("Вы подписались на уведомления!") |
|
|
else: |
|
|
await message.reply("Вы уже подписаны на уведомления!") |
|
|
|
|
|
async def send_notification_to_user(chat_id, text, media_path=None): |
|
|
""" |
|
|
Асинхронная функция для отправки уведомления одному пользователю. |
|
|
""" |
|
|
try: |
|
|
if media_path and os.path.exists(media_path): |
|
|
|
|
|
with open(media_path, 'rb') as photo: |
|
|
await bot.send_photo(chat_id, photo=InputFile(photo), caption=text) |
|
|
elif text: |
|
|
|
|
|
await bot.send_message(chat_id, text=text) |
|
|
else: |
|
|
print(f"Skipping sending to {chat_id}: No text or media provided.") |
|
|
|
|
|
except Exception as e: |
|
|
|
|
|
print(f"Failed to send message to {chat_id}: {e}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def send_notification_to_all(text, media_path=None): |
|
|
""" |
|
|
Асинхронная функция для отправки уведомления всем подписанным пользователям. |
|
|
""" |
|
|
print(f"Attempting to send notification to {len(subscribed_users)} users.") |
|
|
|
|
|
users_to_notify = list(subscribed_users) |
|
|
for chat_id in users_to_notify: |
|
|
await send_notification_to_user(chat_id, text, media_path) |
|
|
|
|
|
await asyncio.sleep(0.05) |
|
|
|
|
|
|
|
|
app = Flask(__name__) |
|
|
|
|
|
|
|
|
ADMIN_HTML = """ |
|
|
<!doctype html> |
|
|
<html lang="ru"> |
|
|
<head> |
|
|
<meta charset="utf-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
|
|
<title>Админ панель бота</title> |
|
|
<style> |
|
|
body { |
|
|
font-family: sans-serif; |
|
|
line-height: 1.6; |
|
|
margin: 20px; |
|
|
background-color: #f8f8f8; |
|
|
color: #333; |
|
|
} |
|
|
.container { |
|
|
max-width: 600px; |
|
|
margin: 0 auto; |
|
|
background-color: #fff; |
|
|
padding: 20px; |
|
|
border-radius: 8px; |
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
|
|
} |
|
|
h1 { |
|
|
text-align: center; |
|
|
color: #0056b3; |
|
|
} |
|
|
form { |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
} |
|
|
label { |
|
|
margin-bottom: 5px; |
|
|
font-weight: bold; |
|
|
} |
|
|
textarea, input[type="file"] { |
|
|
margin-bottom: 15px; |
|
|
padding: 10px; |
|
|
border: 1px solid #ccc; |
|
|
border-radius: 4px; |
|
|
font-size: 1rem; |
|
|
} |
|
|
textarea { |
|
|
resize: vertical; |
|
|
min-height: 100px; |
|
|
} |
|
|
button { |
|
|
padding: 10px 15px; |
|
|
background-color: #007bff; |
|
|
color: white; |
|
|
border: none; |
|
|
border-radius: 4px; |
|
|
cursor: pointer; |
|
|
font-size: 1rem; |
|
|
transition: background-color 0.3s ease; |
|
|
} |
|
|
button:hover { |
|
|
background-color: #0056b3; |
|
|
} |
|
|
.flash-message { |
|
|
padding: 10px; |
|
|
margin-bottom: 15px; |
|
|
border-radius: 4px; |
|
|
background-color: #d4edda; |
|
|
color: #155724; |
|
|
border: 1px solid #c3e6cb; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<h1>Отправить уведомление</h1> |
|
|
{% with messages = get_flashed_messages() %} |
|
|
{% if messages %} |
|
|
{% for message in messages %} |
|
|
<div class="flash-message">{{ message }}</div> |
|
|
{% endfor %} |
|
|
{% endif %} |
|
|
{% endwith %} |
|
|
<p>Подписано пользователей: {{ user_count }}</p> |
|
|
<form action="{{ url_for('send_notification') }}" method="post" enctype="multipart/form-data"> |
|
|
<label for="message">Текст уведомления:</label> |
|
|
<textarea id="message" name="message" placeholder="Введите текст уведомления"></textarea> |
|
|
|
|
|
<label for="media">Медиафайл (фото):</label> |
|
|
<input type="file" id="media" name="media" accept="image/*"> |
|
|
|
|
|
<button type="submit">Отправить всем</button> |
|
|
</form> |
|
|
</div> |
|
|
</body> |
|
|
</html> |
|
|
""" |
|
|
|
|
|
from flask import flash |
|
|
|
|
|
@app.route('/admin') |
|
|
def admin_panel(): |
|
|
""" |
|
|
Отображает страницу админ панели. |
|
|
""" |
|
|
return render_template_string(ADMIN_HTML, user_count=len(subscribed_users)) |
|
|
|
|
|
@app.route('/send_notification', methods=['POST']) |
|
|
def send_notification(): |
|
|
""" |
|
|
Обрабатывает отправку уведомления из админ панели. |
|
|
""" |
|
|
message_text = request.form.get('message', '').strip() |
|
|
media_file = request.files.get('media') |
|
|
media_path = None |
|
|
|
|
|
|
|
|
if media_file and media_file.filename != '': |
|
|
|
|
|
filename = str(uuid.uuid4()) + os.path.splitext(media_file.filename)[1] |
|
|
media_path = os.path.join(TEMP_UPLOAD_FOLDER, filename) |
|
|
try: |
|
|
media_file.save(media_path) |
|
|
print(f"Saved media file to {media_path}") |
|
|
except Exception as e: |
|
|
print(f"Error saving file: {e}") |
|
|
flash("Ошибка при сохранении медиафайла.", "error") |
|
|
return redirect(url_for('admin_panel')) |
|
|
|
|
|
|
|
|
if not message_text and not media_path: |
|
|
flash("Пожалуйста, введите текст или прикрепите медиафайл.", "warning") |
|
|
if media_path and os.path.exists(media_path): |
|
|
os.remove(media_path) |
|
|
return redirect(url_for('admin_panel')) |
|
|
|
|
|
|
|
|
|
|
|
try: |
|
|
future = asyncio.run_coroutine_threadsafe( |
|
|
send_notification_to_all(message_text, media_path), |
|
|
loop |
|
|
) |
|
|
|
|
|
|
|
|
future.result() |
|
|
flash(f"Уведомление отправлено {len(subscribed_users)} пользователям.", "success") |
|
|
print("Notification sent successfully.") |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Error sending notification: {e}") |
|
|
flash(f"Произошла ошибка при отправке: {e}", "error") |
|
|
|
|
|
finally: |
|
|
|
|
|
if media_path and os.path.exists(media_path): |
|
|
try: |
|
|
os.remove(media_path) |
|
|
print(f"Cleaned up temporary file {media_path}") |
|
|
except Exception as e: |
|
|
print(f"Error cleaning up file {media_path}: {e}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return redirect(url_for('admin_panel')) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def start_bot_polling(): |
|
|
""" |
|
|
Функция для запуска поллинга бота в отдельном потоке. |
|
|
""" |
|
|
print("Starting bot polling...") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loop.run_until_complete(dp.start_polling()) |
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
|
|
app.secret_key = 'super secret key for flask notifications' |
|
|
|
|
|
|
|
|
bot_thread = threading.Thread(target=start_bot_polling, daemon=True) |
|
|
bot_thread.start() |
|
|
|
|
|
|
|
|
print(f"Starting Flask admin panel on http://{FLASK_HOST}:{FLASK_PORT}/admin") |
|
|
|
|
|
|
|
|
app.run(host=FLASK_HOST, port=FLASK_PORT, use_reloader=False) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|