diff --git a/olgram/commands/bot_actions.py b/olgram/commands/bot_actions.py index efb289f..f1f670a 100644 --- a/olgram/commands/bot_actions.py +++ b/olgram/commands/bot_actions.py @@ -1,11 +1,13 @@ """ Здесь работа с конкретным ботом """ +import asyncio from aiogram import types from aiogram.utils.exceptions import TelegramAPIError, Unauthorized from aiogram import Bot as AioBot -from olgram.models.models import Bot +from olgram.models.models import Bot, User from server.server import unregister_token +from typing import Optional from locales.locale import _ @@ -87,6 +89,28 @@ async def select_chat(bot: Bot, call: types.CallbackQuery, chat: str): await call.answer(_("Выбран чат {0}").format(chat_obj.name)) +async def start_broadcast(bot: Bot, call: types.CallbackQuery, text: Optional[str]): + if not text: + return await call.answer(_("Отправьте текст для рассылки")) + + user_chat_ids = await User.all().values_list("telegram_id", flat=True) + a_bot = AioBot(bot.decrypted_token()) + count = 0 + await call.answer(_("Рассылка начата")) + try: + for telegram_id in user_chat_ids: + try: + if await a_bot.send_message(telegram_id, text, parse_mode="HTML"): + count += 1 + except Unauthorized: + continue + else: + await asyncio.sleep(0.05) # 20 messages per second (Limit: 30 messages per second) + finally: + await call.bot.send_message(call.from_user.id, _("Рассылка закончена. Сообщений отправлено: {0}").format(count)) + await a_bot.session.close() + + async def threads(bot: Bot, call: types.CallbackQuery): bot.enable_threads = not bot.enable_threads await bot.save(update_fields=["enable_threads"]) diff --git a/olgram/commands/info.py b/olgram/commands/info.py index fdce6ee..b1766dd 100644 --- a/olgram/commands/info.py +++ b/olgram/commands/info.py @@ -37,4 +37,4 @@ async def info(message: types.Message, state: FSMContext): _("Входящих сообщений у всех ботов: {0}\n").format(income_messages) + _("Исходящих сообщений у всех ботов: {0}\n").format(outgoing_messages) + _("Промо-кодов выдано: {0}\n").format(promo_count) + - _("Рекламную плашку выключили: {0}\n".format(olgram_text_disabled))) + _("Рекламную плашку выключили: {0}\n").format(olgram_text_disabled)) diff --git a/olgram/commands/menu.py b/olgram/commands/menu.py index 9f1b095..9d188e7 100644 --- a/olgram/commands/menu.py +++ b/olgram/commands/menu.py @@ -119,14 +119,19 @@ async def send_bot_menu(bot: Bot, call: types.CallbackQuery): chat=empty)) ) keyboard.insert( - types.InlineKeyboardButton(text=_("<< Назад"), - callback_data=menu_callback.new(level=0, bot_id=empty, operation=empty, chat=empty)) + types.InlineKeyboardButton(text=_("Рассылка"), + callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="broadcast", + chat=empty)) ) keyboard.insert( types.InlineKeyboardButton(text=_("Опции"), callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="settings", chat=empty)) ) + keyboard.insert( + types.InlineKeyboardButton(text=_("<< Назад"), + callback_data=menu_callback.new(level=0, bot_id=empty, operation=empty, chat=empty)) + ) await edit_or_create(call, dedent(_(""" Управление ботом @{0}. @@ -202,6 +207,41 @@ async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery): await edit_or_create(call, text, reply_markup=keyboard, parse_mode="HTML") +async def send_bot_broadcast_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None, + chat_id: ty.Optional[int] = None, text: ty.Optional[str] = None): + if call: + await call.answer() + keyboard = types.InlineKeyboardMarkup(row_width=2) + keyboard.insert( + types.InlineKeyboardButton(text=_("<< Назад"), + callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty)) + ) + keyboard.insert( + types.InlineKeyboardButton(text=_("Начать рассылку"), + callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="start_broadcast", + chat=empty)) + ) + + if text: + text = dedent(_(""" + Сейчас вы редактируете текст, который будет отправлен всем пользователям бота @{0} после начала рассылки. + + Текущий текст: +
+ {1}
+
+ Отправьте сообщение, чтобы изменить текст.
+ """)).format(bot.name, text)
+ else:
+ text = _(
+ "Отправьте сообщение с текстом, который будет отправлен всем пользователям бота @{0} после начала рассылки."
+ ).format(bot.name)
+ if call:
+ await edit_or_create(call, text, keyboard, parse_mode="HTML")
+ else:
+ await AioBot.get_current().send_message(chat_id, text, reply_markup=keyboard, parse_mode="HTML")
+
+
async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None, chat_id: ty.Optional[int] = None):
if call:
await call.answer()
@@ -342,7 +382,17 @@ async def send_bot_templates_menu(bot: Bot, call: ty.Optional[types.CallbackQuer
await AioBot.get_current().send_message(chat_id, text, reply_markup=keyboard, parse_mode="HTML")
-@dp.message_handler(state="wait_start_text", content_types="text", regexp="^[^/].+") # Not command
+@dp.message_handler(state="wait_broadcast_text", content_types="text", regexp="^[^/].*") # Not command
+async def broadcast_text_received(message: types.Message, state: FSMContext):
+ broadcast_text = message.html_text
+ async with state.proxy() as proxy:
+ bot_id = proxy.get("bot_id")
+ proxy["broadcast_text"] = broadcast_text
+ bot = await Bot.get_or_none(pk=bot_id)
+ await send_bot_broadcast_menu(bot, chat_id=message.chat.id, text=broadcast_text)
+
+
+@dp.message_handler(state="wait_start_text", content_types="text", regexp="^[^/].*") # Not command
async def start_text_received(message: types.Message, state: FSMContext):
async with state.proxy() as proxy:
bot_id = proxy.get("bot_id")
@@ -352,7 +402,7 @@ async def start_text_received(message: types.Message, state: FSMContext):
await send_bot_text_menu(bot, chat_id=message.chat.id)
-@dp.message_handler(state="wait_second_text", content_types="text", regexp="^[^/].+") # Not command
+@dp.message_handler(state="wait_second_text", content_types="text", regexp="^[^/].*") # Not command
async def second_text_received(message: types.Message, state: FSMContext):
async with state.proxy() as proxy:
bot_id = proxy.get("bot_id")
@@ -362,7 +412,7 @@ async def second_text_received(message: types.Message, state: FSMContext):
await send_bot_second_text_menu(bot, chat_id=message.chat.id)
-@dp.message_handler(state="wait_template", content_types="text", regexp="^[^/](.+)?") # Not command
+@dp.message_handler(state="wait_template", content_types="text", regexp="^[^/].*") # Not command
async def template_received(message: types.Message, state: FSMContext):
async with state.proxy() as proxy:
bot_id = proxy.get("bot_id")
@@ -423,6 +473,11 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
return await send_bot_statistic_menu(bot, call)
if operation == "settings":
return await send_bot_settings_menu(bot, call)
+ if operation == "broadcast":
+ await state.set_state("wait_broadcast_text")
+ async with state.proxy() as proxy:
+ proxy["bot_id"] = bot.id
+ return await send_bot_broadcast_menu(bot, call)
if operation == "text":
await state.set_state("wait_start_text")
async with state.proxy() as proxy:
@@ -457,6 +512,10 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
if operation == "reset_second_text":
await bot_actions.reset_bot_second_text(bot, call)
return await send_bot_second_text_menu(bot, call)
+ if operation == "start_broadcast":
+ async with state.proxy() as proxy:
+ text = proxy.get("broadcast_text")
+ return await bot_actions.start_broadcast(bot, call, text)
if operation == "templates":
await state.set_state("wait_template")
async with state.proxy() as proxy:
diff --git a/olgram/settings.py b/olgram/settings.py
index 704ff7c..dd7d2a6 100644
--- a/olgram/settings.py
+++ b/olgram/settings.py
@@ -91,8 +91,8 @@ class ServerSettings(AbstractSettings):
return "/cert/public.pem"
@classmethod
- def append_text(cls) -> str:
- return "\n\nЭтот бот создан с помощью @OlgramBot"
+ def append_text(cls, _: ty.Callable) -> str:
+ return _("\n\nЭтот бот создан с помощью @OlgramBot")
@classmethod
@lru_cache
diff --git a/server/custom.py b/server/custom.py
index d17aed6..d58f66e 100644
--- a/server/custom.py
+++ b/server/custom.py
@@ -213,14 +213,13 @@ async def handle_operator_message(message: types.Message, super_chat_id: int, bo
async def message_handler(message: types.Message, *args, **kwargs):
- _ = _get_translator(message)
bot = db_bot_instance.get()
if message.text and message.text == "/start":
# На команду start нужно ответить, не пересылая сообщение никуда
text = bot.start_text
if bot.enable_olgram_text:
- text += _(ServerSettings.append_text())
+ text += ServerSettings.append_text(_get_translator(message))
return SendMessage(chat_id=message.chat.id, text=text, parse_mode="HTML")
if message.text and message.text == "/security_policy":