mirror of
https://github.com/civsocit/olgram.git
synced 2025-05-24 11:23:25 +00:00
mailing first iteration
This commit is contained in:
parent
acb62fb644
commit
f26958518c
|
@ -67,3 +67,17 @@ Olgram пересылает сообщения так, чтобы сообщен
|
||||||
|
|
||||||
По-умолчанию поток сообщений от одного пользователя прерывается каждые 24 часа. Без этой опции поток сообщений не
|
По-умолчанию поток сообщений от одного пользователя прерывается каждые 24 часа. Без этой опции поток сообщений не
|
||||||
прерывается никогда.
|
прерывается никогда.
|
||||||
|
|
||||||
|
|
||||||
|
.. _mailing:
|
||||||
|
|
||||||
|
Рассылка
|
||||||
|
---------------
|
||||||
|
|
||||||
|
После включения этой опции ваш бот будет запоминать всех пользователей, которые пишут в ваш бот.
|
||||||
|
Вы сможете запустить рассылку по этим пользователям.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Включение этой опции меняет текст политики конфиденциальности вашего feedback бота (команда /security_policy)
|
||||||
|
и может отпугнуть некоторых пользователей. Не включайте эту опцию без необходимости.
|
||||||
|
|
|
@ -128,3 +128,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"])
|
||||||
|
|
|
@ -178,6 +178,11 @@ async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery):
|
||||||
operation="always_second_message",
|
operation="always_second_message",
|
||||||
chat=empty))
|
chat=empty))
|
||||||
)
|
)
|
||||||
|
keyboard.insert(
|
||||||
|
types.InlineKeyboardButton(text=_("Рассылка"),
|
||||||
|
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="mailing",
|
||||||
|
chat=empty))
|
||||||
|
)
|
||||||
keyboard.insert(
|
keyboard.insert(
|
||||||
types.InlineKeyboardButton(text=_("Прерывать поток"),
|
types.InlineKeyboardButton(text=_("Прерывать поток"),
|
||||||
callback_data=menu_callback.new(level=3, bot_id=bot.id,
|
callback_data=menu_callback.new(level=3, bot_id=bot.id,
|
||||||
|
@ -203,14 +208,16 @@ async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery):
|
||||||
antiflood_turn = _("включен") if bot.enable_antiflood else _("выключен")
|
antiflood_turn = _("включен") if bot.enable_antiflood else _("выключен")
|
||||||
enable_always_second_message = _("включён") if bot.enable_always_second_message else _("выключен")
|
enable_always_second_message = _("включён") if bot.enable_always_second_message else _("выключен")
|
||||||
thread_interrupt = _("да") if bot.enable_thread_interrupt else _("нет")
|
thread_interrupt = _("да") if bot.enable_thread_interrupt 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>
|
||||||
<a href="https://olgram.readthedocs.io/ru/latest/options.html#always_second_message">Автоответчик всегда</a>: <b>{3}
|
<a href="https://olgram.readthedocs.io/ru/latest/options.html#always_second_message">Автоответчик всегда</a>: <b>{3}</b>
|
||||||
<a href="https://olgram.readthedocs.io/ru/latest/options.html#thread_interrupt">Прерывать поток</a>: <b>{4}
|
<a href="https://olgram.readthedocs.io/ru/latest/options.html#thread_interrupt">Прерывать поток</a>: <b>{4}</b>
|
||||||
</b>
|
<a href="https://olgram.readthedocs.io/ru/latest/options.html#mailing">Рассылка</a>: <b>{5}</b>
|
||||||
""")).format(thread_turn, info_turn, antiflood_turn, enable_always_second_message, thread_interrupt)
|
""")).format(thread_turn, info_turn, antiflood_turn, enable_always_second_message, thread_interrupt,
|
||||||
|
mailing_turn)
|
||||||
|
|
||||||
if is_promo:
|
if is_promo:
|
||||||
olgram_turn = _("включена") if bot.enable_olgram_text else _("выключена")
|
olgram_turn = _("включена") if bot.enable_olgram_text else _("выключена")
|
||||||
|
@ -543,6 +550,9 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
|
||||||
if operation == "always_second_message":
|
if operation == "always_second_message":
|
||||||
await bot_actions.always_second_message(bot, call)
|
await bot_actions.always_second_message(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 == "thread_interrupt":
|
if operation == "thread_interrupt":
|
||||||
await bot_actions.thread_interrupt(bot, call)
|
await bot_actions.thread_interrupt(bot, call)
|
||||||
return await send_bot_settings_menu(bot, call)
|
return await send_bot_settings_menu(bot, call)
|
||||||
|
|
6
olgram/migrations/models/20_20240301215418_update.sql
Normal file
6
olgram/migrations/models/20_20240301215418_update.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
-- upgrade --
|
||||||
|
ALTER TABLE "bot" ADD "enable_mailing" BOOL NOT NULL DEFAULT False;
|
||||||
|
ALTER TABLE "bot" ADD "last_mailing_at" TIMESTAMPTZ;
|
||||||
|
-- downgrade --
|
||||||
|
ALTER TABLE "bot" DROP COLUMN "enable_mailing";
|
||||||
|
ALTER TABLE "bot" DROP COLUMN "last_mailing_at";
|
|
@ -48,6 +48,8 @@ class Bot(Model):
|
||||||
enable_antiflood = fields.BooleanField(default=False)
|
enable_antiflood = fields.BooleanField(default=False)
|
||||||
enable_always_second_message = fields.BooleanField(default=False)
|
enable_always_second_message = fields.BooleanField(default=False)
|
||||||
enable_thread_interrupt = fields.BooleanField(default=True)
|
enable_thread_interrupt = fields.BooleanField(default=True)
|
||||||
|
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()
|
||||||
|
@ -106,6 +108,17 @@ class User(Model):
|
||||||
table = 'user'
|
table = 'user'
|
||||||
|
|
||||||
|
|
||||||
|
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 GroupChat(Model):
|
class GroupChat(Model):
|
||||||
id = fields.IntField(pk=True)
|
id = fields.IntField(pk=True)
|
||||||
chat_id = fields.BigIntField(index=True, unique=True)
|
chat_id = fields.BigIntField(index=True, unique=True)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup
|
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup
|
||||||
from aiogram.utils.exceptions import TelegramAPIError
|
from aiogram.utils.exceptions import TelegramAPIError
|
||||||
|
from aiogram import types, Bot as AioBot
|
||||||
|
|
||||||
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")
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from aiogram import Bot as AioBot, Dispatcher
|
from aiogram import Bot as AioBot, Dispatcher
|
||||||
from aiogram.dispatcher.webhook import WebhookRequestHandler
|
from aiogram.dispatcher.webhook import WebhookRequestHandler
|
||||||
from aiogram.dispatcher.webhook import SendMessage
|
from aiogram.dispatcher.webhook import SendMessage
|
||||||
|
@ -11,7 +13,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, BotStartMessage, BotSecondMessage
|
from olgram.models.models import Bot, GroupChat, BannedUser, BotStartMessage, BotSecondMessage, 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 +57,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,
|
||||||
|
@ -130,11 +137,19 @@ async def send_to_superchat(is_super_group: bool, message: types.Message, super_
|
||||||
await send_user_message(message, super_chat_id, bot)
|
await send_user_message(message, super_chat_id, bot)
|
||||||
|
|
||||||
|
|
||||||
|
async def _increase_count(_bot):
|
||||||
|
_bot.incoming_messages_count = F("incoming_messages_count") + 1
|
||||||
|
await _bot.save(update_fields=["incoming_messages_count"])
|
||||||
|
|
||||||
|
|
||||||
async def handle_user_message(message: types.Message, super_chat_id: int, bot):
|
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:
|
||||||
|
asyncio.create_task(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:
|
||||||
|
@ -157,8 +172,7 @@ async def handle_user_message(message: types.Message, super_chat_id: int, bot):
|
||||||
_logger.error(f"(exception on forwarding) {err}")
|
_logger.error(f"(exception on forwarding) {err}")
|
||||||
return
|
return
|
||||||
|
|
||||||
bot.incoming_messages_count = F("incoming_messages_count") + 1
|
asyncio.create_task(_increase_count(bot))
|
||||||
await bot.save(update_fields=["incoming_messages_count"])
|
|
||||||
|
|
||||||
# И отправить пользователю специальный текст, если он указан и если давно не отправляли
|
# И отправить пользователю специальный текст, если он указан и если давно не отправляли
|
||||||
if bot.second_text:
|
if bot.second_text:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user