mailing first iteration

This commit is contained in:
walker 2022-11-06 04:32:32 +04:00
parent bb1456dda1
commit 340246b937
6 changed files with 70 additions and 6 deletions

View File

@ -49,3 +49,16 @@ Olgram пересылает сообщения так, чтобы сообщен
При включении этой опции пользователю запрещается отправлять больше одного сообщения в минуту. Используйте её, если При включении этой опции пользователю запрещается отправлять больше одного сообщения в минуту. Используйте её, если
не успеваете обрабатывать входящие сообщения. не успеваете обрабатывать входящие сообщения.
.. _mailing:
Рассылка
---------------
После включения этой опции ваш бот будет запоминать всех пользователей, которые пишут в ваш бот.
Вы сможете запустить рассылку по этим пользователям.
.. note::
Включение этой опции меняет текст политики конфиденциальности вашего feedback бота (команда /security_policy)
и может отпугнуть некоторых пользователей. Не включайте эту опцию без необходимости.

View File

@ -106,3 +106,8 @@ async def olgram_text(bot: Bot, call: types.CallbackQuery):
async def antiflood(bot: Bot, call: types.CallbackQuery): async def antiflood(bot: Bot, call: types.CallbackQuery):
bot.enable_antiflood = not bot.enable_antiflood bot.enable_antiflood = not bot.enable_antiflood
await bot.save(update_fields=["enable_antiflood"]) await bot.save(update_fields=["enable_antiflood"])
async def mailing(bot: Bot, call: types.CallbackQuery):
bot.enable_mailing = not bot.enable_mailing
await bot.save(update_fields=["enable_mailing"])

View File

@ -172,6 +172,11 @@ async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery):
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="antiflood", callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="antiflood",
chat=empty)) chat=empty))
) )
keyboard.insert(
types.InlineKeyboardButton(text=_("Рассылка"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="mailing",
chat=empty))
)
is_promo = await bot.is_promo() is_promo = await bot.is_promo()
if is_promo: if is_promo:
keyboard.insert( keyboard.insert(
@ -189,11 +194,13 @@ async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery):
thread_turn = _("включены") if bot.enable_threads else _("выключены") thread_turn = _("включены") if bot.enable_threads else _("выключены")
info_turn = _("включены") if bot.enable_additional_info else _("выключены") info_turn = _("включены") if bot.enable_additional_info else _("выключены")
antiflood_turn = _("включен") if bot.enable_antiflood else _("выключен") antiflood_turn = _("включен") if bot.enable_antiflood else _("выключен")
mailing_turn = _("включена") if bot.enable_mailing else _("выключена")
text = dedent(_(""" text = dedent(_("""
<a href="https://olgram.readthedocs.io/ru/latest/options.html#threads">Потоки сообщений</a>: <b>{0}</b> <a href="https://olgram.readthedocs.io/ru/latest/options.html#threads">Потоки сообщений</a>: <b>{0}</b>
<a href="https://olgram.readthedocs.io/ru/latest/options.html#user-info">Данные пользователя</a>: <b>{1}</b> <a href="https://olgram.readthedocs.io/ru/latest/options.html#user-info">Данные пользователя</a>: <b>{1}</b>
<a href="https://olgram.readthedocs.io/ru/latest/options.html#antiflood">Антифлуд</a>: <b>{2}</b> <a href="https://olgram.readthedocs.io/ru/latest/options.html#antiflood">Антифлуд</a>: <b>{2}</b>
""")).format(thread_turn, info_turn, antiflood_turn) <a href="https://olgram.readthedocs.io/ru/latest/options.html#mailing">Рассылка</a>: <b>{3}</b>
""")).format(thread_turn, info_turn, antiflood_turn, mailing_turn)
if is_promo: if is_promo:
olgram_turn = _("включена") if bot.enable_olgram_text else _("выключена") olgram_turn = _("включена") if bot.enable_olgram_text else _("выключена")
@ -440,6 +447,9 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
if operation == "antiflood": if operation == "antiflood":
await bot_actions.antiflood(bot, call) await bot_actions.antiflood(bot, call)
return await send_bot_settings_menu(bot, call) return await send_bot_settings_menu(bot, call)
if operation == "mailing":
await bot_actions.mailing(bot, call)
return await send_bot_settings_menu(bot, call)
if operation == "additional_info": if operation == "additional_info":
await bot_actions.additional_info(bot, call) await bot_actions.additional_info(bot, call)
return await send_bot_settings_menu(bot, call) return await send_bot_settings_menu(bot, call)

View File

@ -0,0 +1,14 @@
-- upgrade --
ALTER TABLE "bot" ADD "last_mailing_at" TIMESTAMPTZ;
ALTER TABLE "bot" ADD "enable_mailing" BOOL NOT NULL DEFAULT False;
CREATE TABLE IF NOT EXISTS "mailinguser" (
"id" BIGSERIAL NOT NULL PRIMARY KEY,
"telegram_id" BIGINT NOT NULL,
"bot_id" INT NOT NULL REFERENCES "bot" ("id") ON DELETE CASCADE,
CONSTRAINT "uid_mailinguser_bot_id_906a76" UNIQUE ("bot_id", "telegram_id")
);
CREATE INDEX IF NOT EXISTS "idx_mailinguser_telegra_55de60" ON "mailinguser" ("telegram_id");;
-- downgrade --
ALTER TABLE "bot" DROP COLUMN "last_mailing_at";
ALTER TABLE "bot" DROP COLUMN "enable_mailing";
DROP TABLE IF EXISTS "mailinguser";

View File

@ -46,6 +46,8 @@ class Bot(Model):
enable_additional_info = fields.BooleanField(default=False) enable_additional_info = fields.BooleanField(default=False)
enable_olgram_text = fields.BooleanField(default=True) enable_olgram_text = fields.BooleanField(default=True)
enable_antiflood = fields.BooleanField(default=False) enable_antiflood = fields.BooleanField(default=False)
enable_mailing = fields.BooleanField(default=False)
last_mailing_at = fields.DatetimeField(null=True, default=None)
def decrypted_token(self): def decrypted_token(self):
cryptor = DatabaseSettings.cryptor() cryptor = DatabaseSettings.cryptor()
@ -70,6 +72,17 @@ class Bot(Model):
table = 'bot' table = 'bot'
class MailingUser(Model):
id = fields.BigIntField(pk=True)
telegram_id = fields.BigIntField(index=True)
bot = fields.ForeignKeyField("models.Bot", related_name="mailing_users", on_delete=fields.relational.CASCADE)
class Meta:
table = 'mailinguser'
unique_together = (("bot", "telegram_id"), )
class User(Model): class User(Model):
id = fields.IntField(pk=True) id = fields.IntField(pk=True)
telegram_id = fields.BigIntField(index=True, unique=True) telegram_id = fields.BigIntField(index=True, unique=True)

View File

@ -11,7 +11,7 @@ from tortoise.expressions import F
import logging import logging
import typing as ty import typing as ty
from olgram.settings import ServerSettings from olgram.settings import ServerSettings
from olgram.models.models import Bot, GroupChat, BannedUser from olgram.models.models import Bot, GroupChat, BannedUser, MailingUser
from locales.locale import _, translators from locales.locale import _, translators
from server.inlines import inline_handler from server.inlines import inline_handler
@ -55,15 +55,20 @@ def _on_security_policy(message: types.Message, bot):
text = _("<b>Политика конфиденциальности</b>\n\n" text = _("<b>Политика конфиденциальности</b>\n\n"
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При отправке сообщения (кроме команд " "Этот бот не хранит ваши сообщения, имя пользователя и @username. При отправке сообщения (кроме команд "
"/start и /security_policy) ваш идентификатор пользователя записывается в кеш на некоторое время и потом " "/start и /security_policy) ваш идентификатор пользователя записывается в кеш на некоторое время и потом "
"удаляется из кеша. Этот идентификатор используется только для общения с оператором; боты Olgram " "удаляется из кеша. Этот идентификатор используется для общения с оператором.\n\n")
"не делают массовых рассылок.\n\n")
if bot.enable_additional_info: if bot.enable_additional_info:
text += _("При отправке сообщения (кроме команд /start и /security_policy) оператор <b>видит</b> ваши имя " text += _("При отправке сообщения (кроме команд /start и /security_policy) оператор <b>видит</b> ваши имя "
"пользователя, @username и идентификатор пользователя в силу настроек, которые оператор указал при " "пользователя, @username и идентификатор пользователя в силу настроек, которые оператор указал при "
"создании бота.") "создании бота.\n\n")
else: else:
text += _("В зависимости от ваших настроек конфиденциальности Telegram, оператор может видеть ваш username, " text += _("В зависимости от ваших настроек конфиденциальности Telegram, оператор может видеть ваш username, "
"имя пользователя и другую информацию.") "имя пользователя и другую информацию.\n\n")
if bot.enable_mailing:
text += _("В этом боте включена массовая рассылка в силу настроек, которые оператор указал при создании бота. "
"Ваш идентификатор пользователя может быть записан в базу данных на долгое время")
else:
text += _("В этом боте нет массовой рассылки сообщений")
return SendMessage(chat_id=message.chat.id, return SendMessage(chat_id=message.chat.id,
text=text, text=text,
@ -128,6 +133,10 @@ async def handle_user_message(message: types.Message, super_chat_id: int, bot):
_ = _get_translator(message) _ = _get_translator(message)
is_super_group = super_chat_id < 0 is_super_group = super_chat_id < 0
# Записать пользователя для рассылки, если она включена
if bot.enable_mailing:
_, __ = await MailingUser.get_or_create(telegram_id=message.chat.id, bot=bot)
# Проверить, не забанен ли пользователь # Проверить, не забанен ли пользователь
banned = await bot.banned_users.filter(telegram_id=message.chat.id) banned = await bot.banned_users.filter(telegram_id=message.chat.id)
if banned: if banned: