1
0
mirror of https://github.com/civsocit/olgram.git synced 2025-12-17 13:36:17 +00:00

9 Commits

Author SHA1 Message Date
er8dd
acb62fb644 "interrupt threads" option 2024-03-01 19:39:46 +04:00
jjki3d
ae45374490 flake8 2024-02-17 04:30:05 +04:00
jjki3d
0881c86349 second message option 2024-02-17 03:53:43 +04:00
jjki3d
82b68d1d9f multi-lang fix 2024-01-13 02:29:36 +04:00
jjki3d
a5e6fbce34 multi-lang 2024-01-12 22:35:01 +04:00
walker
28ed36ffeb rm xmr 2023-06-23 12:43:18 +04:00
walker
601c16622d update address 2023-06-17 02:38:37 +04:00
walker
9e46041d0f update year 2023-02-14 23:04:30 +04:00
walker
f41e17a15c rebuild 2023-02-14 22:59:58 +04:00
23 changed files with 1117 additions and 901 deletions

25
.readthedocs.yaml Normal file
View File

@@ -0,0 +1,25 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.8"
jobs:
post_create_environment:
- python -m pip install sphinx_rtd_theme
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/source/conf.py

View File

@@ -1,6 +1,7 @@
О проекте О проекте
=================================== ===================================
Зачем нужен Olgram Зачем нужен Olgram
------------ ------------

View File

@@ -4,21 +4,25 @@
Донаты Донаты
---------------- ----------------
На рекламу проекта, аренду сервера и пиццу На аренду сервера для этого проекта
Bitcoin: Bitcoin:
``bc1qlq7cm5chc8flr3fy8ewk967aknq3dwmxtwn9hl`` ``bc1qlq7cm5chc8flr3fy8ewk967aknq3dwmxtwn9hl``
Monero: Litecoin:
``886AQ8tCVcQKp21xsuSLkfDdTAdtCFH1jR58Tw9MsaxFXoZ7YRHXx1cQcUfUnDX6hySzPsQEVt6RWPn3sXH9QUmwCr3oVqB`` ``LTC1QXAJSVZ0LW44AA5NYTUCH8CP2G8X7A4CDASE4Y7``
Dash: Как убрать "Этот бот создан с помощью ..."
``XqxetfWzr5n4Ms1TxMbdEEeHGe8CaMdmb6`` ----------------
Напишите нам на `@civsocit_feedback_bot <https://t.me/civsocit_feedback_bot>`_.
История изменений История изменений
---------------- ----------------
- `2024-03-01` Непрерывные потоки сообщений (опция)
- `2024-02-17` Опция смены режима работы автоответчика: автоответчик отвечает на КАЖДОЕ сообщение
- `2024-01-12` Мультиязычность (стартовое сообщение и автоответчик)
- `2022-08-01` Защита от флуда - `2022-08-01` Защита от флуда
- `2022-07-23` Автоответчик не пишет сообщение лишний раз - `2022-07-23` Автоответчик не пишет сообщение лишний раз
- `2022-07-04` Поддержка двух ботов в одном чате - `2022-07-04` Поддержка двух ботов в одном чате

View File

@@ -3,7 +3,7 @@
# -- Project information # -- Project information
project = 'Olgram' project = 'Olgram'
copyright = '2022, Civsocit' copyright = '2024, Civsocit'
author = 'civsocit' author = 'civsocit'
release = '0.1' release = '0.1'

View File

@@ -50,15 +50,20 @@ Olgram пересылает сообщения так, чтобы сообщен
При включении этой опции пользователю запрещается отправлять больше одного сообщения в минуту. Используйте её, если При включении этой опции пользователю запрещается отправлять больше одного сообщения в минуту. Используйте её, если
не успеваете обрабатывать входящие сообщения. не успеваете обрабатывать входящие сообщения.
.. _mailing:
Рассылка .. _always_second_message:
---------------
После включения этой опции ваш бот будет запоминать всех пользователей, которые пишут в ваш бот. Использовать автоответчик всегда
Вы сможете запустить рассылку по этим пользователям. --------------------------------
.. note:: По-умолчанию автоответчик отвечает только на первое сообщение в диалоге с пользователем. Чтобы автоответчик отвечал на
КАЖДОЕ входящее сообщение, включите эту опцию.
Включение этой опции меняет текст политики конфиденциальности вашего feedback бота (команда /security_policy)
и может отпугнуть некоторых пользователей. Не включайте эту опцию без необходимости. .. thread_interrupt:
Прерывать поток
--------------------------------
По-умолчанию поток сообщений от одного пользователя прерывается каждые 24 часа. Без этой опции поток сообщений не
прерывается никогда.

View File

@@ -68,6 +68,13 @@ BotFather - это официальный бот Telegram, создающий д
Теперь просто отправьте новый текст приветствия. Теперь просто отправьте новый текст приветствия.
.. note::
Чтобы настроить особый текст приветствия для, например, русскоязычных пользователей (т.е. тех пользователей, у
которых в настройках Telegram выставлена русская локализация), нажмите кнопку "Руссикй 🇷🇺" и только потом отправьте
текст приветствия. Чтобы отредактировать текст приветствия для всех остальных языков, нажмите "[все языки]".
Как привязать бота к групповому чату Как привязать бота к групповому чату
------------------------------------ ------------------------------------

View File

@@ -455,7 +455,7 @@ msgid ""
"отключено.\n" "отключено.\n"
"\n" "\n"
" Текущий текст:\n" " Текущий текст:\n"
" <pre>\n" " <pre>"
" {1}\n" " {1}\n"
" </pre>\n" " </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n" " Отправьте сообщение, чтобы изменить текст.\n"
@@ -711,11 +711,11 @@ msgstr "(Re)launch the bot"
msgid "Политика конфиденциальности" msgid "Политика конфиденциальности"
msgstr "Privacy Policy" msgstr "Privacy Policy"
#~ msgid "" msgid ""
#~ "\n" "\n"
#~ "\n" "\n"
#~ "Этот бот создан с помощью @OlgramBot" "Этот бот создан с помощью @OlgramBot"
#~ msgstr "" msgstr ""
#~ "\n" "\n"
#~ "\n" "\n"
#~ "This bot was created using @OlgramBot" "This bot was created using @OlgramBot"

View File

@@ -407,9 +407,7 @@ msgid ""
" команду /start\n" " команду /start\n"
"\n" "\n"
" Текущий текст:\n" " Текущий текст:\n"
" <pre>\n" " <pre>{1}</pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n" " Отправьте сообщение, чтобы изменить текст.\n"
" " " "
msgstr "" msgstr ""
@@ -419,9 +417,7 @@ msgstr ""
" команду /start\n" " команду /start\n"
"\n" "\n"
" Поточний текст:\n" " Поточний текст:\n"
" <pre>\n" " <pre>{1}</pre>\n"
" {1}\n"
" </pre>\n"
" Надішліть повідомлення, щоб змінити текст.\n" " Надішліть повідомлення, щоб змінити текст.\n"
" \n" " \n"
" " " "
@@ -462,9 +458,7 @@ msgid ""
"отключено.\n" "отключено.\n"
"\n" "\n"
" Текущий текст:\n" " Текущий текст:\n"
" <pre>\n" " <pre>{1}</pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n" " Отправьте сообщение, чтобы изменить текст.\n"
" " " "
msgstr "" msgstr ""
@@ -723,11 +717,11 @@ msgstr "(Пере) запустити бота"
msgid "Политика конфиденциальности" msgid "Политика конфиденциальности"
msgstr "Політика конфіденційності" msgstr "Політика конфіденційності"
#~ msgid "" msgid ""
#~ "\n" "\n"
#~ "\n" "\n"
#~ "Этот бот создан с помощью @OlgramBot" "Этот бот создан с помощью @OlgramBot"
#~ msgstr "" msgstr ""
#~ "\n" "\n"
#~ "\n" "\n"
#~ "Цей бот створено за допомогою @OlgramBot" "Цей бот створено за допомогою @OlgramBot"

View File

@@ -306,9 +306,7 @@ msgid ""
" команду /start\n" " команду /start\n"
"\n" "\n"
" Текущий текст:\n" " Текущий текст:\n"
" <pre>\n" " <pre>{1}</pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n" " Отправьте сообщение, чтобы изменить текст.\n"
" " " "
msgstr "" msgstr ""
@@ -317,9 +315,7 @@ msgstr ""
" /start\n" " /start\n"
"\n" "\n"
" 目前的文本。\n" " 目前的文本。\n"
" <pre>\n" " <pre>{1}</pre>\n"
" {1}\n"
" </pre>\n"
" 发送消息,改变文本。\n" " 发送消息,改变文本。\n"
" " " "
@@ -359,9 +355,7 @@ msgid ""
"отключено.\n" "отключено.\n"
"\n" "\n"
" Текущий текст:\n" " Текущий текст:\n"
" <pre>\n" " <pre>{1}</pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n" " Отправьте сообщение, чтобы изменить текст.\n"
" " " "
msgstr "" msgstr ""
@@ -370,9 +364,7 @@ msgstr ""
"默认情况下,它是禁用的。\n" "默认情况下,它是禁用的。\n"
"\n" "\n"
" 目前的文本。\n" " 目前的文本。\n"
" <pre>\n" " <pre>{1}</pre>。\n"
" {1}\n"
" </pre>。\n"
" 发送消息,改变文本。\n" " 发送消息,改变文本。\n"
" " " "

View File

@@ -1,13 +1,10 @@
""" """
Здесь работа с конкретным ботом Здесь работа с конкретным ботом
""" """
from asyncio import sleep
from datetime import datetime
from aiogram import types from aiogram import types
from aiogram.utils import exceptions from aiogram.utils.exceptions import TelegramAPIError, Unauthorized
from aiogram import Bot as AioBot from aiogram import Bot as AioBot
from olgram.models.models import Bot from olgram.models.models import Bot, BotStartMessage, BotSecondMessage
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 _
@@ -18,38 +15,50 @@ async def delete_bot(bot: Bot, call: types.CallbackQuery):
""" """
try: try:
await unregister_token(bot.decrypted_token()) await unregister_token(bot.decrypted_token())
except exceptions.Unauthorized: except Unauthorized:
# Вероятно пользователь сбросил токен или удалил бот, это уже не наши проблемы # Вероятно пользователь сбросил токен или удалил бот, это уже не наши проблемы
pass pass
await bot.delete() await bot.delete()
await call.answer(_("Бот удалён")) await call.answer(_("Бот удалён"))
try: try:
await call.message.delete() await call.message.delete()
except exceptions.TelegramAPIError: except TelegramAPIError:
pass pass
async def reset_bot_text(bot: Bot, call: types.CallbackQuery): async def reset_bot_text(bot: Bot, call: types.CallbackQuery, state):
""" """
Пользователь решил сбросить текст бота к default Пользователь решил сбросить текст бота к default
:param bot: :param bot:
:param call: :param call:
:return: :return:
""" """
bot.start_text = bot._meta.fields_map['start_text'].default async with state.proxy() as proxy:
await bot.save() lang = proxy.get("lang", "none")
if lang == "none":
await BotStartMessage.filter(bot=bot).delete()
bot.start_text = bot._meta.fields_map['start_text'].default
await bot.save(update_fields=["start_text"])
else:
await BotStartMessage.filter(bot=bot, locale=lang).delete()
await call.answer(_("Текст сброшен")) await call.answer(_("Текст сброшен"))
async def reset_bot_second_text(bot: Bot, call: types.CallbackQuery): async def reset_bot_second_text(bot: Bot, call: types.CallbackQuery, state):
""" """
Пользователь решил сбросить second text бота Пользователь решил сбросить second text бота
:param bot: :param bot:
:param call: :param call:
:return: :return:
""" """
bot.second_text = bot._meta.fields_map['second_text'].default async with state.proxy() as proxy:
await bot.save() lang = proxy.get("lang", "none")
if lang == "none":
await BotSecondMessage.filter(bot=bot).delete()
bot.second_text = bot._meta.fields_map['second_text'].default
await bot.save(update_fields=["second_text"])
else:
await BotSecondMessage.filter(bot=bot, locale=lang).delete()
await call.answer(_("Текст сброшен")) await call.answer(_("Текст сброшен"))
@@ -75,7 +84,7 @@ async def select_chat(bot: Bot, call: types.CallbackQuery, chat: str):
try: try:
await chat.delete() await chat.delete()
await a_bot.leave_chat(chat.chat_id) await a_bot.leave_chat(chat.chat_id)
except exceptions.TelegramAPIError: except TelegramAPIError:
pass pass
await call.answer(_("Бот вышел из чатов")) await call.answer(_("Бот вышел из чатов"))
await a_bot.session.close() await a_bot.session.close()
@@ -100,6 +109,16 @@ async def additional_info(bot: Bot, call: types.CallbackQuery):
await bot.save(update_fields=["enable_additional_info"]) await bot.save(update_fields=["enable_additional_info"])
async def always_second_message(bot: Bot, call: types.CallbackQuery):
bot.enable_always_second_message = not bot.enable_always_second_message
await bot.save(update_fields=["enable_always_second_message"])
async def thread_interrupt(bot: Bot, call: types.CallbackQuery):
bot.enable_thread_interrupt = not bot.enable_thread_interrupt
await bot.save(update_fields=["enable_thread_interrupt"])
async def olgram_text(bot: Bot, call: types.CallbackQuery): async def olgram_text(bot: Bot, call: types.CallbackQuery):
if await bot.is_promo(): if await bot.is_promo():
bot.enable_olgram_text = not bot.enable_olgram_text bot.enable_olgram_text = not bot.enable_olgram_text
@@ -109,35 +128,3 @@ 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"])
async def go_mailing(bot: Bot, context) -> int:
users = await bot.mailing_users
a_bot = AioBot(bot.decrypted_token())
count = 0
print(f"start mailing {context}")
for user in users:
bot.last_mailing_at = datetime.now()
await bot.save(update_fields=["last_mailing_at"])
try:
await sleep(0.05)
try:
await send_stored_message(context, a_bot, user.telegram_id)
except exceptions.RetryAfter as err:
await sleep(err.timeout)
await send_stored_message(context, a_bot, user.telegram_id)
count += 1
except (exceptions.ChatNotFound, exceptions.BotBlocked, exceptions.UserDeactivated):
await user.delete()
except exceptions.TelegramAPIError:
pass
return count

View File

@@ -1,17 +1,17 @@
from olgram.router import dp from olgram.router import dp
from aiogram import types, Bot as AioBot from aiogram import types, Bot as AioBot
from olgram.models.models import Bot, User, DefaultAnswer from olgram.models.models import Bot, User, DefaultAnswer, BotStartMessage, BotSecondMessage
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, send_stored_message from olgram.utils.mix import edit_or_create, button_text_limit, wrap
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,12 +127,6 @@ 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}.
@@ -179,8 +173,15 @@ async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery):
chat=empty)) chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text=_("Рассылка"), types.InlineKeyboardButton(text=_("Автоответчик всегда"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="mailing", callback_data=menu_callback.new(level=3, bot_id=bot.id,
operation="always_second_message",
chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text=_("Прерывать поток"),
callback_data=menu_callback.new(level=3, bot_id=bot.id,
operation="thread_interrupt",
chat=empty)) chat=empty))
) )
is_promo = await bot.is_promo() is_promo = await bot.is_promo()
@@ -200,13 +201,16 @@ 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 _("выключена") enable_always_second_message = _("включён") if bot.enable_always_second_message else _("выключен")
thread_interrupt = _("да") if bot.enable_thread_interrupt 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#mailing">Рассылка</a>: <b>{3}</b> <a href="https://olgram.readthedocs.io/ru/latest/options.html#always_second_message">Автоответчик всегда</a>: <b>{3}
""")).format(thread_turn, info_turn, antiflood_turn, mailing_turn) <a href="https://olgram.readthedocs.io/ru/latest/options.html#thread_interrupt">Прерывать поток</a>: <b>{4}
</b>
""")).format(thread_turn, info_turn, antiflood_turn, enable_always_second_message, thread_interrupt)
if is_promo: if is_promo:
olgram_turn = _("включена") if bot.enable_olgram_text else _("выключена") olgram_turn = _("включена") if bot.enable_olgram_text else _("выключена")
@@ -215,9 +219,26 @@ async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery):
await edit_or_create(call, text, reply_markup=keyboard, parse_mode="HTML") await edit_or_create(call, 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): languages = {
"en": "English 🇺🇸",
"ru": "Русский 🇷🇺",
"uk": "Український 🇺🇦",
"tr": "Türkçe 🇹🇷",
"hy": "հայերեն 🇦🇲",
"ka": "ქართული ენა 🇬🇪"
}
async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None, chat_id: ty.Optional[int] = None,
state=None):
if call: if call:
await call.answer() await call.answer()
async with state.proxy() as proxy:
lang = proxy.get("lang", "none")
prepared_languages = {ln.locale: ln.text for ln in await bot.start_texts}
keyboard = types.InlineKeyboardMarkup(row_width=2) keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text=_("<< Завершить редактирование"), types.InlineKeyboardButton(text=_("<< Завершить редактирование"),
@@ -228,47 +249,40 @@ async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] =
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="next_text", callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="next_text",
chat=empty)) chat=empty))
) )
keyboard.insert( keyboard.row(
types.InlineKeyboardButton(text=_("Сбросить текст"), types.InlineKeyboardButton(text=_("Сбросить текст"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="reset_text", callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="reset_text",
chat=empty)) chat=empty))
) )
keyboard.add(
types.InlineKeyboardButton(text=("🟢 " if lang == "none" else "") + _("[все языки]"),
callback_data=menu_callback.new(level=3, bot_id=bot.id,
operation="slang_none", chat=empty))
)
for code, name in languages.items():
prefix = ""
if code == lang:
prefix = "🟢 "
elif code in prepared_languages:
prefix = "✔️ "
keyboard.insert(
types.InlineKeyboardButton(text=prefix + name,
callback_data=menu_callback.new(level=3, bot_id=bot.id,
operation=f"slang_{code}",
chat=empty))
)
text = dedent(_(""" text = dedent(_("""
Сейчас вы редактируете текст, который отправляется после того, как пользователь отправит вашему боту @{0} Сейчас вы редактируете текст, который отправляется после того, как пользователь отправит вашему боту @{0}
команду /start команду /start
Текущий текст: Текущий текст{2}:
<pre> <pre>{1}</pre>
{1}
</pre>
Отправьте сообщение, чтобы изменить текст. Отправьте сообщение, чтобы изменить текст.
""")) """))
text = text.format(bot.name, bot.start_text) text = text.format(bot.name,
if call: prepared_languages.get(lang, bot.start_text),
await edit_or_create(call, text, keyboard, parse_mode="HTML") _(" (для языка {0})").format(languages[lang]) if lang != "none" else "")
else:
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: if call:
await edit_or_create(call, text, keyboard, parse_mode="HTML") await edit_or_create(call, text, keyboard, parse_mode="HTML")
else: else:
@@ -301,9 +315,15 @@ async def send_bot_statistic_menu(bot: Bot, call: ty.Optional[types.CallbackQuer
async def send_bot_second_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None, async def send_bot_second_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None,
chat_id: ty.Optional[int] = None): chat_id: ty.Optional[int] = None, state=None):
if call: if call:
await call.answer() await call.answer()
async with state.proxy() as proxy:
lang = proxy.get("lang", "none")
prepared_languages = {ln.locale: ln.text for ln in await bot.second_texts}
keyboard = types.InlineKeyboardMarkup(row_width=2) keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text=_("<< Завершить редактирование"), types.InlineKeyboardButton(text=_("<< Завершить редактирование"),
@@ -324,18 +344,35 @@ async def send_bot_second_text_menu(bot: Bot, call: ty.Optional[types.CallbackQu
callback_data=menu_callback.new(level=3, bot_id=bot.id, callback_data=menu_callback.new(level=3, bot_id=bot.id,
operation="reset_second_text", chat=empty)) operation="reset_second_text", chat=empty))
) )
keyboard.add(
types.InlineKeyboardButton(text=("🟢 " if lang == "none" else "") + _("[все языки]"),
callback_data=menu_callback.new(level=3, bot_id=bot.id,
operation="alang_none", chat=empty))
)
for code, name in languages.items():
prefix = ""
if code == lang:
prefix = "🟢 "
elif code in prepared_languages:
prefix = "✔️ "
keyboard.insert(
types.InlineKeyboardButton(text=prefix + name,
callback_data=menu_callback.new(level=3, bot_id=bot.id,
operation=f"alang_{code}",
chat=empty))
)
text = dedent(_(""" text = dedent(_("""
Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в ответ на все входящие сообщения @{0} \ Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в ответ на все входящие сообщения @{0} \
автоматически. По умолчанию оно отключено. автоматически. По умолчанию оно отключено.
Текущий текст: Текущий текст{2}:
<pre> <pre>{1}</pre>
{1}
</pre>
Отправьте сообщение, чтобы изменить текст. Отправьте сообщение, чтобы изменить текст.
""")) """))
text = text.format(bot.name, bot.second_text if bot.second_text else _("(отключено)")) text = text.format(bot.name,
prepared_languages.get(lang, bot.second_text or _("отключено")),
_(" (для языка {0})").format(languages[lang]) if lang != "none" else "")
if call: if call:
await edit_or_create(call, text, keyboard, parse_mode="HTML") await edit_or_create(call, text, keyboard, parse_mode="HTML")
else: else:
@@ -383,63 +420,43 @@ async def send_bot_templates_menu(bot: Bot, call: ty.Optional[types.CallbackQuer
async def start_text_received(message: types.Message, state: FSMContext): async def start_text_received(message: types.Message, state: FSMContext):
async with state.proxy() as proxy: async with state.proxy() as proxy:
bot_id = proxy.get("bot_id") bot_id = proxy.get("bot_id")
lang = proxy.get("lang", "none")
bot = await Bot.get_or_none(pk=bot_id) bot = await Bot.get_or_none(pk=bot_id)
bot.start_text = message.html_text if lang == "none":
await bot.save() bot.start_text = message.html_text
await send_bot_text_menu(bot, chat_id=message.chat.id) await bot.save(update_fields=["start_text"])
else:
obj, created = await BotStartMessage.get_or_create(bot=bot,
@dp.message_handler(state="wait_mailing_text", locale=lang,
content_types=[types.ContentType.TEXT, defaults={"text": message.html_text})
types.ContentType.LOCATION, if not created:
types.ContentType.DOCUMENT, obj.text = message.html_text
types.ContentType.PHOTO, await obj.save(update_fields=["text"])
types.ContentType.AUDIO, await send_bot_text_menu(bot, chat_id=message.chat.id, state=state)
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=1)
keyboard.insert(
types.InlineKeyboardButton(text=_("Да, начать рассылку"),
callback_data=menu_callback.new(level=3, 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:
bot_id = proxy.get("bot_id") bot_id = proxy.get("bot_id")
lang = proxy.get("lang", "none")
bot = await Bot.get_or_none(pk=bot_id) bot = await Bot.get_or_none(pk=bot_id)
bot.second_text = message.html_text if lang == "none":
await bot.save() bot.second_text = message.html_text
await send_bot_second_text_menu(bot, chat_id=message.chat.id) await bot.save(update_fields=["second_text"])
else:
obj, created = await BotSecondMessage.get_or_create(bot=bot,
locale=lang,
defaults={"text": message.html_text})
if not created:
obj.text = message.html_text
await obj.save(update_fields=["text"])
if not bot.second_text:
bot.second_text = message.html_text
await bot.save(update_fields=["second_text"])
await send_bot_second_text_menu(bot, chat_id=message.chat.id, state=state)
@dp.message_handler(state="wait_template", content_types="text", regexp="^[^/](.+)?") # Not command @dp.message_handler(state="wait_template", content_types="text", regexp="^[^/](.+)?") # Not command
@@ -503,20 +520,11 @@ 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 == "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 not await bot.mailing_users:
return await call.answer(_("Нет пользователей для рассылки"))
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 == "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:
proxy["bot_id"] = bot.id proxy["bot_id"] = bot.id
return await send_bot_text_menu(bot, call) return await send_bot_text_menu(bot, call, state=state)
if level == "3": if level == "3":
if operation == "delete_yes": if operation == "delete_yes":
@@ -529,40 +537,41 @@ 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)
if operation == "always_second_message":
await bot_actions.always_second_message(bot, call)
return await send_bot_settings_menu(bot, call)
if operation == "thread_interrupt":
await bot_actions.thread_interrupt(bot, call)
return await send_bot_settings_menu(bot, call)
if operation == "olgram_text": if operation == "olgram_text":
await bot_actions.olgram_text(bot, call) await bot_actions.olgram_text(bot, call)
return await send_bot_settings_menu(bot, call) return await send_bot_settings_menu(bot, call)
if operation == "reset_text": if operation == "reset_text":
await bot_actions.reset_bot_text(bot, call) await bot_actions.reset_bot_text(bot, call, state)
return await send_bot_text_menu(bot, call) return await send_bot_text_menu(bot, call, state=state)
if operation.startswith("slang_"):
async with state.proxy() as proxy:
lang = operation.replace("slang_", "")
if lang == "none" or lang in languages:
proxy["lang"] = lang
return await send_bot_text_menu(bot, call, state=state)
if operation == "next_text": if operation == "next_text":
await state.set_state("wait_second_text") await state.set_state("wait_second_text")
async with state.proxy() as proxy: async with state.proxy() as proxy:
proxy["bot_id"] = bot.id proxy["bot_id"] = bot.id
return await send_bot_second_text_menu(bot, call) return await send_bot_second_text_menu(bot, call, state=state)
if operation == "go_go_mailing": if operation.startswith("alang_"):
if (await state.get_state()) == "wait_mailing_text": async with state.proxy() as proxy:
async with state.proxy() as proxy: lang = operation.replace("alang_", "")
mailing_data = dict(proxy) if lang == "none" or lang in languages:
await state.reset_state() proxy["lang"] = lang
return await send_bot_second_text_menu(bot, call, state=state)
if bot.last_mailing_at and bot.last_mailing_at >= datetime.now() - timedelta(minutes=5):
return await call.answer(_("Рассылка была совсем недавно, подождите немного"), show_alert=True)
if not await bot.mailing_users:
return await call.answer(_("Нет пользователей для рассылки"))
await call.answer(_("Рассылка запущена"))
count = await bot_actions.go_mailing(bot, mailing_data)
await call.message.answer(_("Рассылка завершена, отправлено {0} сообщений").format(count))
if operation == "reset_second_text": if operation == "reset_second_text":
await bot_actions.reset_bot_second_text(bot, call) await bot_actions.reset_bot_second_text(bot, call, state)
return await send_bot_second_text_menu(bot, call) return await send_bot_second_text_menu(bot, call, state=state)
if operation == "templates": if operation == "templates":
await state.set_state("wait_template") await state.set_state("wait_template")
async with state.proxy() as proxy: async with state.proxy() as proxy:

View File

@@ -1,14 +0,0 @@
-- 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

@@ -0,0 +1,9 @@
-- upgrade --
CREATE TABLE IF NOT EXISTS "bot_start_message" (
"id" SERIAL NOT NULL PRIMARY KEY,
"locale" VARCHAR(5) NOT NULL,
"bot_id" INT NOT NULL REFERENCES "bot" ("id") ON DELETE CASCADE,
CONSTRAINT "uid_bot_start_m_bot_id_871cd1" UNIQUE ("bot_id", "locale")
);
-- downgrade --
DROP TABLE IF EXISTS "bot_start_message";

View File

@@ -0,0 +1,4 @@
-- upgrade --
ALTER TABLE "bot_start_message" ADD "text" TEXT NOT NULL;
-- downgrade --
ALTER TABLE "bot_start_message" DROP COLUMN "text";

View File

@@ -0,0 +1,10 @@
-- upgrade --
CREATE TABLE IF NOT EXISTS "bot_second_message" (
"id" SERIAL NOT NULL PRIMARY KEY,
"locale" VARCHAR(5) NOT NULL,
"text" TEXT NOT NULL,
"bot_id" INT NOT NULL REFERENCES "bot" ("id") ON DELETE CASCADE,
CONSTRAINT "uid_bot_second__bot_id_432892" UNIQUE ("bot_id", "locale")
);
-- downgrade --
DROP TABLE IF EXISTS "bot_second_message";

View File

@@ -0,0 +1,4 @@
-- upgrade --
ALTER TABLE "bot" ADD "enable_always_second_message" BOOL NOT NULL DEFAULT False;
-- downgrade --
ALTER TABLE "bot" DROP COLUMN "enable_always_second_message";

View File

@@ -0,0 +1,4 @@
-- upgrade --
ALTER TABLE "bot" ADD "enable_thread_interrupt" BOOL NOT NULL DEFAULT True;
-- downgrade --
ALTER TABLE "bot" DROP COLUMN "enable_thread_interrupt";

View File

@@ -46,8 +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) enable_always_second_message = fields.BooleanField(default=False)
last_mailing_at = fields.DatetimeField(null=True, default=None) enable_thread_interrupt = fields.BooleanField(default=True)
def decrypted_token(self): def decrypted_token(self):
cryptor = DatabaseSettings.cryptor() cryptor = DatabaseSettings.cryptor()
@@ -72,15 +72,26 @@ class Bot(Model):
table = 'bot' table = 'bot'
class MailingUser(Model): class BotStartMessage(Model):
id = fields.BigIntField(pk=True) id = fields.IntField(pk=True)
telegram_id = fields.BigIntField(index=True) bot = fields.ForeignKeyField("models.Bot", related_name="start_texts", on_delete=fields.CASCADE)
locale = fields.CharField(max_length=5)
bot = fields.ForeignKeyField("models.Bot", related_name="mailing_users", on_delete=fields.relational.CASCADE) text = fields.TextField()
class Meta: class Meta:
table = 'mailinguser' unique_together = ("bot", "locale")
unique_together = (("bot", "telegram_id"), ) table = 'bot_start_message'
class BotSecondMessage(Model):
id = fields.IntField(pk=True)
bot = fields.ForeignKeyField("models.Bot", related_name="second_texts", on_delete=fields.CASCADE)
locale = fields.CharField(max_length=5)
text = fields.TextField()
class Meta:
unique_together = ("bot", "locale")
table = 'bot_second_message'
class User(Model): class User(Model):

View File

@@ -41,7 +41,7 @@ class OlgramSettings(AbstractSettings):
@classmethod @classmethod
def version(cls): def version(cls):
return "0.5.0" return "0.7.1"
@classmethod @classmethod
@lru_cache @lru_cache

View File

@@ -1,5 +1,4 @@
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
@@ -31,23 +30,3 @@ 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")

1378
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
/usr/lib/python3.10/Tools/i18n/pygettext.py -d chinese -o locales/olgram.pot olgram/ server/ /usr/lib/python3.11/Tools/i18n/pygettext.py -d chinese -o locales/olgram.pot olgram/ server/

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, MailingUser from olgram.models.models import Bot, GroupChat, BannedUser, BotStartMessage, BotSecondMessage
from locales.locale import _, translators from locales.locale import _, translators
from server.inlines import inline_handler from server.inlines import inline_handler
@@ -55,20 +55,15 @@ 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) ваш идентификатор пользователя записывается в кеш на некоторое время и потом "
"удаляется из кеша. Этот идентификатор используется для общения с оператором.\n\n") "удаляется из кеша. Этот идентификатор используется только для общения с оператором; боты Olgram "
"не делают массовых рассылок.\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,
@@ -83,47 +78,54 @@ async def send_user_message(message: types.Message, super_chat_id: int, bot):
if message.from_user.username: if message.from_user.username:
user_info += " | @" + message.from_user.username user_info += " | @" + message.from_user.username
user_info += f" | #ID{message.from_user.id}" user_info += f" | #ID{message.from_user.id}"
if message.from_user.locale:
user_info += f" | lang: {message.from_user.locale}"
if message.forward_sender_name:
user_info += f" | fwd: {message.forward_sender_name}"
# Добавлять информацию в конец текста # Добавлять информацию в конец текста
if message.content_type == types.ContentType.TEXT and len(message.text) + len(user_info) < 4093: # noqa:E721 if message.content_type == types.ContentType.TEXT \
and len(message.text) + len(user_info) < 4093: # noqa:E721
new_message = await message.bot.send_message(super_chat_id, message.text + "\n\n" + user_info) new_message = await message.bot.send_message(super_chat_id, message.text + "\n\n" + user_info)
else: # Не добавлять информацию в конец текста, информация отдельным сообщением else: # Не добавлять информацию в конец текста, информация отдельным сообщением
new_message = await message.bot.send_message(super_chat_id, text=user_info) new_message = await message.bot.send_message(super_chat_id, text=user_info)
new_message_2 = await message.copy_to(super_chat_id, reply_to_message_id=new_message.message_id) new_message_2 = await message.copy_to(super_chat_id, reply_to_message_id=new_message.message_id)
await _redis.set(_message_unique_id(bot.pk, new_message_2.message_id), message.chat.id, await _redis.set(_message_unique_id(bot.pk, new_message_2.message_id), message.chat.id,
pexpire=ServerSettings.redis_timeout_ms()) pexpire=ServerSettings.redis_timeout_ms())
await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id,
pexpire=ServerSettings.redis_timeout_ms())
return new_message
else: else:
try: try:
new_message = await message.forward(super_chat_id) new_message = await message.forward(super_chat_id)
except exceptions.MessageCantBeForwarded: except exceptions.MessageCantBeForwarded:
new_message = await message.copy_to(super_chat_id) new_message = await message.copy_to(super_chat_id)
await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id,
pexpire=ServerSettings.redis_timeout_ms()) await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id,
return new_message pexpire=ServerSettings.redis_timeout_ms())
return new_message
async def send_to_superchat(is_super_group: bool, message: types.Message, super_chat_id: int, bot): async def send_to_superchat(is_super_group: bool, message: types.Message, super_chat_id: int, bot):
"""Пересылка сообщения от пользователя оператору (логика потоков сообщений)""" """Пересылка сообщения от пользователя оператору (логика потоков сообщений)"""
if is_super_group and bot.enable_threads: if is_super_group and bot.enable_threads:
if bot.enable_thread_interrupt:
thread_timeout = ServerSettings.thread_timeout_ms()
else:
thread_timeout = ServerSettings.redis_timeout_ms()
thread_first_message = await _redis.get(_thread_uniqie_id(bot.pk, message.chat.id)) thread_first_message = await _redis.get(_thread_uniqie_id(bot.pk, message.chat.id))
if thread_first_message: if thread_first_message:
# переслать в супер-чат, отвечая на предыдущее сообщение # переслать в супер-чат, отвечая на предыдущее сообщение
try: try:
new_message = await message.copy_to(super_chat_id, reply_to_message_id=int(thread_first_message)) new_message = await message.copy_to(super_chat_id, reply_to_message_id=int(thread_first_message))
await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id, await _redis.set(_message_unique_id(bot.pk, new_message.message_id), message.chat.id,
pexpire=ServerSettings.redis_timeout_ms()) pexpire=thread_timeout)
except exceptions.BadRequest: except exceptions.BadRequest:
new_message = await send_user_message(message, super_chat_id, bot) new_message = await send_user_message(message, super_chat_id, bot)
await _redis.set(_thread_uniqie_id(bot.pk, message.chat.id), new_message.message_id, await _redis.set(
pexpire=ServerSettings.thread_timeout_ms()) _thread_uniqie_id(bot.pk, message.chat.id), new_message.message_id, pexpire=thread_timeout)
else: else:
# переслать супер-чат # переслать супер-чат
new_message = await send_user_message(message, super_chat_id, bot) new_message = await send_user_message(message, super_chat_id, bot)
await _redis.set(_thread_uniqie_id(bot.pk, message.chat.id), new_message.message_id, await _redis.set(_thread_uniqie_id(bot.pk, message.chat.id), new_message.message_id,
pexpire=ServerSettings.thread_timeout_ms()) pexpire=thread_timeout)
else: # личные сообщения не поддерживают потоки сообщений: просто отправляем сообщение else: # личные сообщения не поддерживают потоки сообщений: просто отправляем сообщение
await send_user_message(message, super_chat_id, bot) await send_user_message(message, super_chat_id, bot)
@@ -133,10 +135,6 @@ 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:
@@ -166,8 +164,10 @@ async def handle_user_message(message: types.Message, super_chat_id: int, bot):
if bot.second_text: if bot.second_text:
send_auto = not await _redis.get(_last_message_uid(bot.pk, message.chat.id)) send_auto = not await _redis.get(_last_message_uid(bot.pk, message.chat.id))
await _redis.setex(_last_message_uid(bot.pk, message.chat.id), 60 * 60 * 3, 1) await _redis.setex(_last_message_uid(bot.pk, message.chat.id), 60 * 60 * 3, 1)
if send_auto: if send_auto or bot.enable_always_second_message:
return SendMessage(chat_id=message.chat.id, text=bot.second_text, parse_mode="HTML") text_obj = await BotSecondMessage.get_or_none(bot=bot, locale=str(message.from_user.locale))
return SendMessage(chat_id=message.chat.id, text=text_obj.text if text_obj else bot.second_text,
parse_mode="HTML")
async def handle_operator_message(message: types.Message, super_chat_id: int, bot): async def handle_operator_message(message: types.Message, super_chat_id: int, bot):
@@ -227,7 +227,8 @@ async def message_handler(message: types.Message, *args, **kwargs):
if message.text and message.text == "/start": if message.text and message.text == "/start":
# На команду start нужно ответить, не пересылая сообщение никуда # На команду start нужно ответить, не пересылая сообщение никуда
text = bot.start_text text_obj = await BotStartMessage.get_or_none(bot=bot, locale=str(message.from_user.locale))
text = text_obj.text if text_obj else bot.start_text
if bot.enable_olgram_text: if bot.enable_olgram_text:
text += _(ServerSettings.append_text()) text += _(ServerSettings.append_text())
return SendMessage(chat_id=message.chat.id, text=text, parse_mode="HTML") return SendMessage(chat_id=message.chat.id, text=text, parse_mode="HTML")