mailing second iteration

This commit is contained in:
walker 2022-11-06 06:09:15 +04:00
parent 340246b937
commit 696bc5368b
3 changed files with 137 additions and 3 deletions

View File

@ -1,10 +1,12 @@
""" """
Здесь работа с конкретным ботом Здесь работа с конкретным ботом
""" """
from asyncio import sleep
from aiogram import types from aiogram import types
from aiogram.utils.exceptions import TelegramAPIError, Unauthorized from aiogram.utils.exceptions import TelegramAPIError, Unauthorized, BotKicked, BotBlocked
from aiogram import Bot as AioBot from aiogram import Bot as AioBot
from olgram.models.models import Bot from olgram.models.models import Bot
from olgram.utils.mix import send_stored_message
from server.server import unregister_token from server.server import unregister_token
from locales.locale import _ from locales.locale import _
@ -111,3 +113,22 @@ async def antiflood(bot: Bot, call: types.CallbackQuery):
async def mailing(bot: Bot, call: types.CallbackQuery): async def mailing(bot: Bot, call: types.CallbackQuery):
bot.enable_mailing = not bot.enable_mailing bot.enable_mailing = not bot.enable_mailing
await bot.save(update_fields=["enable_mailing"]) await bot.save(update_fields=["enable_mailing"])
async def go_mailing(bot: Bot, context) -> int:
users = await bot.mailing_users
a_bot = AioBot(bot.decrypted_token())
sended = 0
for user in users:
try:
await sleep(0.2)
await send_stored_message(context, a_bot, user.telegram_id)
sended += 1
except TelegramAPIError:
# TODO:
# delete user
# check error source, check bot, break if bot deleted
continue
return sended

View File

@ -4,14 +4,14 @@ from aiogram import types, Bot as AioBot
from olgram.models.models import Bot, User, DefaultAnswer from olgram.models.models import Bot, User, DefaultAnswer
from aiogram.dispatcher import FSMContext from aiogram.dispatcher import FSMContext
from aiogram.utils.callback_data import CallbackData from aiogram.utils.callback_data import CallbackData
from datetime import datetime, timedelta
from textwrap import dedent from textwrap import dedent
from olgram.utils.mix import edit_or_create, button_text_limit, wrap from olgram.utils.mix import edit_or_create, button_text_limit, wrap, send_stored_message
from olgram.commands import bot_actions from olgram.commands import bot_actions
from locales.locale import _ from locales.locale import _
import typing as ty import typing as ty
menu_callback = CallbackData('menu', 'level', 'bot_id', 'operation', 'chat') menu_callback = CallbackData('menu', 'level', 'bot_id', 'operation', 'chat')
empty = "0" empty = "0"
@ -127,6 +127,12 @@ async def send_bot_menu(bot: Bot, call: types.CallbackQuery):
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="settings", callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="settings",
chat=empty)) chat=empty))
) )
if bot.enable_mailing:
keyboard.insert(
types.InlineKeyboardButton(text=_("Рассылка"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="go_mailing",
chat=empty))
)
await edit_or_create(call, dedent(_(""" await edit_or_create(call, dedent(_("""
Управление ботом @{0}. Управление ботом @{0}.
@ -245,6 +251,30 @@ async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] =
await AioBot.get_current().send_message(chat_id, text, reply_markup=keyboard, parse_mode="HTML") await AioBot.get_current().send_message(chat_id, text, reply_markup=keyboard, parse_mode="HTML")
async def send_bot_mailing_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None,
chat_id: ty.Optional[int] = None):
if call:
await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=1)
keyboard.insert(
types.InlineKeyboardButton(text=_("<< Отменить рассылку"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
)
text = dedent(_("""
Напишите сообщение, которое нужно разослать всем подписчикам вашего бота @{0}.
У сообщения будет до {1} получателей.
Учтите, что
1. Рассылается только одно сообщение за раз (в т.ч. только одна картинка)
2. Когда рассылка запущена, её нельзя отменить
"""))
text = text.format(bot.name, len(await bot.mailing_users))
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_statistic_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None, async def send_bot_statistic_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None,
chat_id: ty.Optional[int] = None): chat_id: ty.Optional[int] = None):
if call: if call:
@ -359,6 +389,54 @@ async def start_text_received(message: types.Message, state: FSMContext):
await send_bot_text_menu(bot, chat_id=message.chat.id) await send_bot_text_menu(bot, chat_id=message.chat.id)
@dp.message_handler(state="wait_mailing_text",
content_types=[types.ContentType.TEXT,
types.ContentType.LOCATION,
types.ContentType.DOCUMENT,
types.ContentType.PHOTO,
types.ContentType.AUDIO,
types.ContentType.VIDEO]) # TODO: not command
async def mailing_text_received(message: types.Message, state: FSMContext):
async with state.proxy() as proxy:
bot_id = proxy["bot_id"]
proxy["mailing_content_type"] = message.content_type
if message.content_type == types.ContentType.TEXT:
proxy["mailing_text"] = message.html_text
elif message.content_type == types.ContentType.LOCATION:
proxy["mailing_location"] = message.location
elif message.content_type == types.ContentType.PHOTO:
proxy["mailing_photo"] = message.photo[0].file_id
proxy["mailing_caption"] = message.caption
elif message.content_type == types.ContentType.DOCUMENT:
proxy["mailing_document"] = message.document.file_id
proxy["mailing_caption"] = message.caption
elif message.content_type == types.ContentType.AUDIO:
proxy["mailing_audio"] = message.audio.file_id
proxy["mailing_caption"] = message.caption
elif message.content_type == types.ContentType.VIDEO:
proxy["mailing_video"] = message.video.file_id
proxy["mailing_video"] = message.caption
_message_id = await send_stored_message(proxy, AioBot.get_current(), message.chat.id)
keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert(
types.InlineKeyboardButton(text=_("<< Нет, отменить"), # TODO: don't move menu back
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=2, bot_id=bot_id, operation="go_go_mailing",
chat=empty))
)
await AioBot.get_current().send_message(message.chat.id, reply_to_message_id=_message_id.message_id,
text="Вы уверены, что хотите разослать это сообщение всем пользователям?",
reply_markup=keyboard)
@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 def second_text_received(message: types.Message, state: FSMContext):
async with state.proxy() as proxy: async with state.proxy() as proxy:
@ -430,6 +508,20 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
return await send_bot_statistic_menu(bot, call) return await send_bot_statistic_menu(bot, call)
if operation == "settings": if operation == "settings":
return await send_bot_settings_menu(bot, call) return await send_bot_settings_menu(bot, call)
if operation in ("go_mailing", "go_go_mailing"):
if bot.last_mailing_at and bot.last_mailing_at >= datetime.now() - timedelta(minutes=5):
return await call.answer(_("Рассылка была совсем недавно, подождите немного"), show_alert=True)
if operation == "go_mailing":
await state.set_state("wait_mailing_text")
async with state.proxy() as proxy:
proxy["bot_id"] = bot.id
return await send_bot_mailing_menu(bot, call)
if operation == "go_go_mailing":
async with state.proxy() as proxy:
mailing_data = dict(proxy)
await state.reset_state() # TODO: double-click protection
await call.answer(_("Рассылка запущена"))
await bot_actions.go_mailing(bot, mailing_data)
if operation == "text": if operation == "text":
await state.set_state("wait_start_text") await state.set_state("wait_start_text")
async with state.proxy() as proxy: async with state.proxy() as proxy:

View File

@ -1,4 +1,5 @@
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup
from aiogram import types, Bot as AioBot
from aiogram.utils.exceptions import TelegramAPIError from aiogram.utils.exceptions import TelegramAPIError
from typing import Optional from typing import Optional
@ -30,3 +31,23 @@ def wrap(data: str, max_len: int) -> str:
def button_text_limit(data: str) -> str: def button_text_limit(data: str) -> str:
return wrap(data, 30) return wrap(data, 30)
async def send_stored_message(storage: dict, bot: AioBot, chat_id: int):
content_type = storage["mailing_content_type"]
if content_type == types.ContentType.TEXT:
return await bot.send_message(chat_id, storage["mailing_text"], parse_mode="HTML")
if content_type == types.ContentType.LOCATION:
return await bot.send_location(chat_id, storage["mailing_location"][0], storage["mailing_location"][1])
if content_type == types.ContentType.AUDIO:
return await bot.send_audio(chat_id, audio=storage["mailing_audio"], caption=storage.get("mailing_caption"))
if content_type == types.ContentType.DOCUMENT:
return await bot.send_document(chat_id, document=storage["mailing_document"],
caption=storage.get("mailing_caption"))
if content_type == types.ContentType.PHOTO:
return await bot.send_photo(chat_id, photo=storage["mailing_photo"],
caption=storage.get("mailing_caption"))
if content_type == types.ContentType.VIDEO:
return await bot.send_video(chat_id, video=storage["mailing_video"],
caption=storage.get("mailing_caption"))
raise NotImplementedError("Mailing, unknown content type")