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

34 Commits

Author SHA1 Message Date
er8dd
2305590899 f 2024-03-22 04:16:38 +04:00
er8dd
a34e633b98 version bump 2024-03-22 04:14:07 +04:00
er8dd
16da3634db telegram anti-flood 2024-03-22 04:13:32 +04:00
er8dd
a0c6c9415e tags 2024-03-22 03:55:50 +04:00
er8dd
dab803a4e8 tags 2024-03-22 03:49:05 +04:00
er8dd
0ceea778fe merge 2024-03-22 03:46:34 +04:00
er8dd
7ce6df7fd9 bot tags 2024-03-22 03:44:34 +04:00
er8dd
59da56d463 locale 2024-03-17 14:35:34 +04:00
er8dd
1c12730a4e oh you are idiot 2024-03-17 14:25:07 +04:00
er8dd
d0b570baa9 version 2024-03-15 03:07:31 +04:00
er8dd
1b1fe239f8 tag first iteration 2024-03-15 02:59:49 +04:00
er8dd
214824db14 typo 2024-03-15 01:46:51 +04:00
er8dd
147fc2a665 docs 2024-03-03 10:21:00 +04:00
er8dd
c027ec656b minor fixes 2024-03-03 00:30:23 +04:00
er8dd
f79a9f317d Merge branch 'main' into stable 2024-03-03 00:10:32 +04:00
er8dd
eeb6c65f36 minor fixes 2024-03-03 00:07:45 +04:00
er8dd
7a0ce10c56 mailing size limit 2024-03-02 20:50:40 +04:00
er8dd
44f39e4de0 Merge branch 'main' into stable 2024-03-02 20:05:23 +04:00
er8dd
120fdef189 mailing second iteration 2024-03-02 20:03:42 +04:00
er8dd
f26958518c mailing first iteration 2024-03-01 22:12:05 +04:00
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
7aacb2e38f Merge branch 'main' into stable 2024-02-17 03:56:11 +04:00
jjki3d
0881c86349 second message option 2024-02-17 03:53:43 +04:00
jjki3d
1dd4d4d7fd Merge branch 'main' into stable 2024-01-13 02:29:57 +04:00
jjki3d
82b68d1d9f multi-lang fix 2024-01-13 02:29:36 +04:00
jjki3d
02e91a596c Merge branch 'main' into stable 2024-01-13 02:20:53 +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
walker
a262d4e488 Merge branch 'main' into stable 2022-11-05 06:11:27 +04:00
mihalin
3e1f89034a fix translations 2022-09-16 21:30:38 +04:00
28 changed files with 1756 additions and 1015 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,7 +1,7 @@
FROM python:3.8-buster FROM python:3.8-buster
ENV PYTHONUNBUFFERED=1 \ ENV PYTHONUNBUFFERED=1 \
POETRY_VERSION=1.1.12 \ POETRY_VERSION=1.5.1 \
POETRY_VIRTUALENVS_CREATE="false" POETRY_VIRTUALENVS_CREATE="false"
RUN apt-get update && \ RUN apt-get update && \

View File

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

View File

@@ -4,21 +4,30 @@
Донаты Донаты
---------------- ----------------
На рекламу проекта, аренду сервера и пиццу На аренду сервера для этого проекта
Bitcoin: Bitcoin:
``bc1qlq7cm5chc8flr3fy8ewk967aknq3dwmxtwn9hl`` ``bc1qlq7cm5chc8flr3fy8ewk967aknq3dwmxtwn9hl``
Monero: Litecoin:
``886AQ8tCVcQKp21xsuSLkfDdTAdtCFH1jR58Tw9MsaxFXoZ7YRHXx1cQcUfUnDX6hySzPsQEVt6RWPn3sXH9QUmwCr3oVqB`` ``ltc1qxajsvz0lw44aa5nytuch8cp2g8x7a4cdase4y7``
Dash: Monero:
``XqxetfWzr5n4Ms1TxMbdEEeHGe8CaMdmb6`` ``84ymMfpw3vxFxsgmYbFURMiZLgQCmhKsZNiZiqZRbpH2WRka2UDjyDVZpX8XH1cZ9d5EghvPXrF5hEuzvK5NvHGE8za4Gmk``
Как убрать "Этот бот создан с помощью ..."
----------------
Напишите нам на `@civsocit_feedback_bot <https://t.me/civsocit_feedback_bot>`_.
История изменений История изменений
---------------- ----------------
- `2024-03-15` Тэги
- `2024-03-02` Рассылки
- `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,6 +50,25 @@ Olgram пересылает сообщения так, чтобы сообщен
При включении этой опции пользователю запрещается отправлять больше одного сообщения в минуту. Используйте её, если При включении этой опции пользователю запрещается отправлять больше одного сообщения в минуту. Используйте её, если
не успеваете обрабатывать входящие сообщения. не успеваете обрабатывать входящие сообщения.
.. _always_second_message:
Использовать автоответчик всегда
--------------------------------
По-умолчанию автоответчик отвечает только на первое сообщение в диалоге с пользователем. Чтобы автоответчик отвечал на
КАЖДОЕ входящее сообщение, включите эту опцию.
.. thread_interrupt:
Прерывать поток
--------------------------------
По-умолчанию поток сообщений от одного пользователя прерывается каждые 24 часа. Без этой опции поток сообщений не
прерывается никогда.
.. _mailing: .. _mailing:
Рассылка Рассылка

View File

@@ -68,6 +68,13 @@ BotFather - это официальный бот Telegram, создающий д
Теперь просто отправьте новый текст приветствия. Теперь просто отправьте новый текст приветствия.
.. note::
Чтобы настроить особый текст приветствия для, например, русскоязычных пользователей (т.е. тех пользователей, у
которых в настройках Telegram выставлена русская локализация), нажмите кнопку "Руссикй 🇷🇺" и только потом отправьте
текст приветствия. Чтобы отредактировать текст приветствия для всех остальных языков, нажмите "[все языки]".
Как привязать бота к групповому чату Как привязать бота к групповому чату
------------------------------------ ------------------------------------
@@ -108,3 +115,14 @@ BotFather - это официальный бот Telegram, создающий д
Если у вас возникли вопросы по использованию бота, или вы нашли ошибку - напишите Если у вас возникли вопросы по использованию бота, или вы нашли ошибку - напишите
нам `@civsocit_feedback_bot <https://t.me/civsocit_feedback_bot>`_. нам `@civsocit_feedback_bot <https://t.me/civsocit_feedback_bot>`_.
Тэги пользователей
------------------
Пользователям можно проставлять теги. Например, в ответ на сообщение пользователя написать:
```
/tag #important
```
Тогда в user info (см. раздел опции) помимо информации о пользователе будет тег #important

View File

@@ -5,8 +5,8 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"POT-Creation-Date: 2022-09-02 05:02+0400\n" "POT-Creation-Date: 2024-03-02 19:47+0400\n"
"PO-Revision-Date: 2022-09-02 05:07+0400\n" "PO-Revision-Date: 2024-03-02 19:48+0400\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: en_US\n" "Language: en_US\n"
@@ -15,7 +15,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 3.1\n" "X-Generator: Poedit 3.4.2\n"
#: olgram/commands/admin.py:21 olgram/commands/info.py:21 #: olgram/commands/admin.py:21 olgram/commands/info.py:21
#: olgram/commands/promo.py:23 olgram/commands/promo.py:39 #: olgram/commands/promo.py:23 olgram/commands/promo.py:39
@@ -62,27 +62,27 @@ msgstr "Cancel"
msgid "Отправлено" msgid "Отправлено"
msgstr "Sent" msgstr "Sent"
#: olgram/commands/bot_actions.py:22 #: olgram/commands/bot_actions.py:27
msgid "Бот удалён" msgid "Бот удалён"
msgstr "Bot removed" msgstr "Bot removed"
#: olgram/commands/bot_actions.py:38 olgram/commands/bot_actions.py:50 #: olgram/commands/bot_actions.py:49 olgram/commands/bot_actions.py:67
msgid "Текст сброшен" msgid "Текст сброшен"
msgstr "Text is reset" msgstr "Text is reset"
#: olgram/commands/bot_actions.py:64 #: olgram/commands/bot_actions.py:81
msgid "Выбран личный чат" msgid "Выбран личный чат"
msgstr "Personal chat selected" msgstr "Personal chat selected"
#: olgram/commands/bot_actions.py:77 #: olgram/commands/bot_actions.py:94
msgid "Бот вышел из чатов" msgid "Бот вышел из чатов"
msgstr "Bot leaved chats" msgstr "Bot leaved chats"
#: olgram/commands/bot_actions.py:83 #: olgram/commands/bot_actions.py:100
msgid "Нельзя привязать бота к этому чату" msgid "Нельзя привязать бота к этому чату"
msgstr "You can't bind a bot to this chat room" msgstr "You can't bind a bot to this chat room"
#: olgram/commands/bot_actions.py:87 #: olgram/commands/bot_actions.py:104
msgid "Выбран чат {0}" msgid "Выбран чат {0}"
msgstr "Selected chat {0}" msgstr "Selected chat {0}"
@@ -195,7 +195,11 @@ msgstr "All bots have outgoing messages: {0}\n"
msgid "Промо-кодов выдано: {0}\n" msgid "Промо-кодов выдано: {0}\n"
msgstr "Promo codes issued: {0}\n" msgstr "Promo codes issued: {0}\n"
#: olgram/commands/menu.py:31 #: olgram/commands/info.py:40
msgid "Рекламную плашку выключили: {0}\n"
msgstr "Ad disabled:: {0}\n"
#: olgram/commands/menu.py:33
msgid "" msgid ""
"\n" "\n"
" У вас нет добавленных ботов.\n" " У вас нет добавленных ботов.\n"
@@ -209,25 +213,25 @@ msgstr ""
" Send the command /addbot to add a bot.\n" " Send the command /addbot to add a bot.\n"
" " " "
#: olgram/commands/menu.py:46 #: olgram/commands/menu.py:48
msgid "Ваши боты" msgid "Ваши боты"
msgstr "Your bots" msgstr "Your bots"
#: olgram/commands/menu.py:67 #: olgram/commands/menu.py:69
msgid "Личные сообщения" msgid "Личные сообщения"
msgstr "Personal messages" msgstr "Personal messages"
#: olgram/commands/menu.py:72 #: olgram/commands/menu.py:74
msgid "❗️ Выйти из всех чатов" msgid "❗️ Выйти из всех чатов"
msgstr "❗️ Leave all chats" msgstr "❗️ Leave all chats"
#: olgram/commands/menu.py:77 olgram/commands/menu.py:122 #: olgram/commands/menu.py:79 olgram/commands/menu.py:124
#: olgram/commands/menu.py:148 olgram/commands/menu.py:184 #: olgram/commands/menu.py:156 olgram/commands/menu.py:209
#: olgram/commands/menu.py:247 #: olgram/commands/menu.py:390
msgid "<< Назад" msgid "<< Назад"
msgstr "<< Back" msgstr "<< Back"
#: olgram/commands/menu.py:83 #: olgram/commands/menu.py:85
msgid "" msgid ""
"\n" "\n"
" Этот бот не добавлен в чаты, поэтому все сообщения будут приходить " " Этот бот не добавлен в чаты, поэтому все сообщения будут приходить "
@@ -249,7 +253,7 @@ msgstr ""
" again.\n" " again.\n"
" " " "
#: olgram/commands/menu.py:90 #: olgram/commands/menu.py:92
msgid "" msgid ""
"\n" "\n"
" В этом разделе вы можете привязать бота @{0} к чату.\n" " В этом разделе вы можете привязать бота @{0} к чату.\n"
@@ -261,27 +265,31 @@ msgstr ""
" Select the chat room where the bot will forward messages.\n" " Select the chat room where the bot will forward messages.\n"
" " " "
#: olgram/commands/menu.py:102 #: olgram/commands/menu.py:104
msgid "Текст" msgid "Текст"
msgstr "Text" msgstr "Text"
#: olgram/commands/menu.py:107 #: olgram/commands/menu.py:109
msgid "Чат" msgid "Чат"
msgstr "Chat" msgstr "Chat"
#: olgram/commands/menu.py:112 #: olgram/commands/menu.py:114
msgid "Удалить бот" msgid "Удалить бот"
msgstr "Delete bot" msgstr "Delete bot"
#: olgram/commands/menu.py:117 #: olgram/commands/menu.py:119
msgid "Статистика" msgid "Статистика"
msgstr "Statistics" msgstr "Statistics"
#: olgram/commands/menu.py:126 #: olgram/commands/menu.py:128
msgid "Опции" msgid "Опции"
msgstr "Options" msgstr "Options"
#: olgram/commands/menu.py:131 #: olgram/commands/menu.py:134 olgram/commands/menu.py:190
msgid "Рассылка"
msgstr "Mailing"
#: olgram/commands/menu.py:139
msgid "" msgid ""
"\n" "\n"
" Управление ботом @{0}.\n" " Управление ботом @{0}.\n"
@@ -299,11 +307,11 @@ msgstr ""
" @civsocit_feedback_bot\n" " @civsocit_feedback_bot\n"
" " " "
#: olgram/commands/menu.py:143 #: olgram/commands/menu.py:151
msgid "Да, удалить бот" msgid "Да, удалить бот"
msgstr "Yes, delete the bot" msgstr "Yes, delete the bot"
#: olgram/commands/menu.py:152 #: olgram/commands/menu.py:160
msgid "" msgid ""
"\n" "\n"
" Вы уверены, что хотите удалить бота @{0}?\n" " Вы уверены, что хотите удалить бота @{0}?\n"
@@ -313,43 +321,73 @@ msgstr ""
" Are you sure you want to delete the bot @{0}?\n" " Are you sure you want to delete the bot @{0}?\n"
" " " "
#: olgram/commands/menu.py:161 #: olgram/commands/menu.py:169
msgid "Потоки сообщений" msgid "Потоки сообщений"
msgstr "Message threads" msgstr "Message threads"
#: olgram/commands/menu.py:166 #: olgram/commands/menu.py:174
msgid "Данные пользователя" msgid "Данные пользователя"
msgstr "User data" msgstr "User data"
#: olgram/commands/menu.py:171 #: olgram/commands/menu.py:179
msgid "Антифлуд" msgid "Антифлуд"
msgstr "Antiflood" msgstr "Antiflood"
#: olgram/commands/menu.py:178 #: olgram/commands/menu.py:184
msgid "Автоответчик всегда"
msgstr "Autorespond always"
#: olgram/commands/menu.py:195
msgid "Прерывать поток"
msgstr "Inteeupt thread"
#: olgram/commands/menu.py:203
msgid "Olgram подпись" msgid "Olgram подпись"
msgstr "Olgram signature" msgstr "Olgram signature"
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190 #: olgram/commands/menu.py:214 olgram/commands/menu.py:215
msgid "включены" msgid "включены"
msgstr "enabled" msgstr "enabled"
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190 #: olgram/commands/menu.py:214 olgram/commands/menu.py:215
msgid "выключены" msgid "выключены"
msgstr "disabled" msgstr "disabled"
#: olgram/commands/menu.py:191 #: olgram/commands/menu.py:216
#, fuzzy #, fuzzy
#| msgid "включены" #| msgid "включены"
msgid "включен" msgid "включен"
msgstr "enabled" msgstr "enabled"
#: olgram/commands/menu.py:191 #: olgram/commands/menu.py:216 olgram/commands/menu.py:217
#, fuzzy #, fuzzy
#| msgid "выключены" #| msgid "выключены"
msgid "выключен" msgid "выключен"
msgstr "disabled" msgstr "disabled"
#: olgram/commands/menu.py:192 #: olgram/commands/menu.py:217
#, fuzzy
#| msgid "включены"
msgid "включён"
msgstr "enabled"
#: olgram/commands/menu.py:218
msgid "да"
msgstr "yes"
#: olgram/commands/menu.py:218
msgid "нет"
msgstr "no"
#: olgram/commands/menu.py:219 olgram/commands/menu.py:231
msgid "включена"
msgstr "enabled"
#: olgram/commands/menu.py:219 olgram/commands/menu.py:231
msgid "выключена"
msgstr "disabled"
#: olgram/commands/menu.py:220
msgid "" msgid ""
"\n" "\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options." " <a href=\"https://olgram.readthedocs.io/ru/latest/options."
@@ -358,6 +396,12 @@ msgid ""
"info\">Данные пользователя</a>: <b>{1}</b>\n" "info\">Данные пользователя</a>: <b>{1}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options." " <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#antiflood\">Антифлуд</a>: <b>{2}</b>\n" "html#antiflood\">Антифлуд</a>: <b>{2}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#always_second_message\">Автоответчик всегда</a>: <b>{3}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#thread_interrupt\">Прерывать поток</a>: <b>{4}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#mailing\">Рассылка</a>: <b>{5}</b>\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
@@ -366,60 +410,86 @@ msgstr ""
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-" " <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-"
"info\">User data</a>: <b>{1}</b>\n" "info\">User data</a>: <b>{1}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options." " <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#antiflood\">Antiflood</a>: <b>{2}</b>" "html#antiflood\">Anti-flood</a>: <b>{2}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#always_second_message\">Autorespond always</a>: <b>{3}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#thread_interrupt\">Interrupt threads</a>: <b>{4}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#mailing\">Mailing</a>: <b>{5}</b>\n"
" "
#: olgram/commands/menu.py:199 #: olgram/commands/menu.py:232
msgid "включена"
msgstr "enabled"
#: olgram/commands/menu.py:199
msgid "выключена"
msgstr "disabled"
#: olgram/commands/menu.py:200
msgid "Olgram подпись: <b>{0}</b>" msgid "Olgram подпись: <b>{0}</b>"
msgstr "Olgram signature: <b>{0}</b>" msgstr "Olgram signature: <b>{0}</b>"
#: olgram/commands/menu.py:210 olgram/commands/menu.py:272 #: olgram/commands/menu.py:259 olgram/commands/menu.py:421
#: olgram/commands/menu.py:314 #: olgram/commands/menu.py:480
msgid "<< Завершить редактирование" msgid "<< Завершить редактирование"
msgstr "<< Finish editing" msgstr "<< Finish editing"
#: olgram/commands/menu.py:214 #: olgram/commands/menu.py:263
msgid "Автоответчик" msgid "Автоответчик"
msgstr "Autoresponder" msgstr "Autoresponder"
#: olgram/commands/menu.py:219 olgram/commands/menu.py:286 #: olgram/commands/menu.py:268 olgram/commands/menu.py:435
msgid "Сбросить текст" msgid "Сбросить текст"
msgstr "Reset text" msgstr "Reset text"
#: olgram/commands/menu.py:224 #: olgram/commands/menu.py:273 olgram/commands/menu.py:440
msgid "[все языки]"
msgstr "[all languages]"
#: olgram/commands/menu.py:290
msgid "" msgid ""
"\n" "\n"
" Сейчас вы редактируете текст, который отправляется после того, как " " Сейчас вы редактируете текст, который отправляется после того, как "
"пользователь отправит вашему боту @{0}\n" "пользователь отправит вашему боту @{0}\n"
" команду /start\n" " команду /start\n"
"\n" "\n"
" Текущий текст:\n" " Текущий текст{2}:\n"
" <pre>\n" " <pre>{1}</pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n" " Отправьте сообщение, чтобы изменить текст.\n"
" " " "
msgstr "" msgstr ""
#: olgram/commands/menu.py:300 olgram/commands/menu.py:467
msgid " (для языка {0})"
msgstr " (for language {0})"
#: olgram/commands/menu.py:313
msgid "<< Отменить рассылку"
msgstr "<< Cancel"
#: olgram/commands/menu.py:317
msgid ""
"\n" "\n"
" You are now editing the text that is sent after the user sends your bot " " Напишите сообщение, которое нужно разослать всем подписчикам вашего бота "
"@{0}\n" "@{0}. \n"
" /start command.\n" " У сообщения будет до {1} получателей. \n"
" Учтите, что\n"
" 1. Рассылается только одно сообщение за раз (в т.ч. только одна "
"картинка)\n"
" 2. Когда рассылка запущена, её нельзя отменить \n"
" "
msgstr ""
"\n" "\n"
" Current text:\n" " Please send mailing message to send all @{0} subscribers. \n"
" <pre>\n" " Message will have up to {1} recipients. \n"
" {1}\n" " Take note:\n"
" </pre>\n" " 1. Only one message per mailing\n"
" Send a message to change the text.\n" " 2.Mailing cant be interrupted \n"
" " " "
#: olgram/commands/menu.py:251 #: olgram/commands/menu.py:367
msgid "Не удалось загрузить файл (слишком большой размер?)"
msgstr ""
#: olgram/commands/menu.py:374
msgid "Да, начать рассылку"
msgstr "Yes, start mailing"
#: olgram/commands/menu.py:394
msgid "" msgid ""
"\n" "\n"
" Статистика по боту @{0}\n" " Статистика по боту @{0}\n"
@@ -439,45 +509,32 @@ msgstr ""
" Banned users: <b>{4}</b>\n" " Banned users: <b>{4}</b>\n"
" " " "
#: olgram/commands/menu.py:276 #: olgram/commands/menu.py:425
msgid "Предыдущий текст" msgid "Предыдущий текст"
msgstr "Previous text" msgstr "Previous text"
#: olgram/commands/menu.py:281 #: olgram/commands/menu.py:430
msgid "Шаблоны ответов..." msgid "Шаблоны ответов..."
msgstr "Answer templates..." msgstr "Answer templates..."
#: olgram/commands/menu.py:291 #: olgram/commands/menu.py:457
msgid "" msgid ""
"\n" "\n"
" Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в " " Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в "
"ответ на все входящие сообщения @{0} автоматически. По умолчанию оно " "ответ на все входящие сообщения @{0} автоматически. По умолчанию оно "
"отключено.\n" "отключено.\n"
"\n" "\n"
" Текущий текст:\n" " Текущий текст{2}:\n"
" <pre>\n" " <pre>{1}</pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n" " Отправьте сообщение, чтобы изменить текст.\n"
" " " "
msgstr "" msgstr ""
"\n"
" You are now editing the autoresponder text. This message is sent in "
"response to all incoming @{0} messages automatically. It is disabled by "
"default.\n"
"\n"
" Current text:\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Send a message to change the text.\n"
" "
#: olgram/commands/menu.py:301 #: olgram/commands/menu.py:466
msgid "(отключено)" msgid "отключено"
msgstr "(disabled)" msgstr "disabled"
#: olgram/commands/menu.py:318 #: olgram/commands/menu.py:484
msgid "" msgid ""
"\n" "\n"
" Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n" " Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n"
@@ -503,30 +560,50 @@ msgstr ""
" To remove a template from the list, send its number in the list (for " " To remove a template from the list, send its number in the list (for "
"example, 4) " "example, 4) "
#: olgram/commands/menu.py:337 #: olgram/commands/menu.py:503
msgid "(нет шаблонов)" msgid "(нет шаблонов)"
msgstr "(no templates)" msgstr "(no templates)"
#: olgram/commands/menu.py:376 #: olgram/commands/menu.py:565
msgid "У вас нет шаблонов, чтобы их удалять" msgid "У вас нет шаблонов, чтобы их удалять"
msgstr "You don't have templates to delete them" msgstr "You don't have templates to delete them"
#: olgram/commands/menu.py:378 #: olgram/commands/menu.py:567
msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}" msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}"
msgstr "To delete a template, enter a number between 0 and {0}" msgstr "To delete a template, enter a number between 0 and {0}"
#: olgram/commands/menu.py:386 #: olgram/commands/menu.py:575
msgid "У вашего бота уже слишком много шаблонов" msgid "У вашего бота уже слишком много шаблонов"
msgstr "Your bot already has too many templates" msgstr "Your bot already has too many templates"
#: olgram/commands/menu.py:390 #: olgram/commands/menu.py:579
msgid "Такой текст уже есть в списке шаблонов" msgid "Такой текст уже есть в списке шаблонов"
msgstr "This text is already in the list of templates" msgstr "This text is already in the list of templates"
#: olgram/commands/menu.py:408 #: olgram/commands/menu.py:597
msgid "У вас нет прав на этого бота" msgid "У вас нет прав на этого бота"
msgstr "You have no permissions to this bot" msgstr "You have no permissions to this bot"
#: olgram/commands/menu.py:617 olgram/commands/menu.py:643
msgid "Рассылка была совсем недавно, подождите немного"
msgstr "Mailing was recently, wait a bit please"
#: olgram/commands/menu.py:619 olgram/commands/menu.py:645
msgid "Нет пользователей для рассылки"
msgstr "No users for mailing"
#: olgram/commands/menu.py:647
msgid "Рассылка запущена"
msgstr "Mailing started"
#: olgram/commands/menu.py:649
msgid "Рассылка завершена, отправлено {0} сообщений"
msgstr "Mailing completed, {0} messages sent"
#: olgram/commands/menu.py:651
msgid "Устарело, создайте новую рассылку"
msgstr "Expired, please create new mailing"
#: olgram/commands/promo.py:27 #: olgram/commands/promo.py:27
msgid "" msgid ""
"Новый промокод\n" "Новый промокод\n"
@@ -551,8 +628,8 @@ msgstr "Promotion code withdrawn"
msgid "" msgid ""
"Укажите аргумент: промокод. Например: <pre>/setpromo my-promo-code</pre>" "Укажите аргумент: промокод. Например: <pre>/setpromo my-promo-code</pre>"
msgstr "" msgstr ""
"Specify the argument: promo code. For example: <pre>/setpromo my-promo-" "Specify the argument: promo code. For example: <pre>/setpromo my-promo-code</"
"code</pre>" "pre>"
#: olgram/commands/promo.py:78 olgram/commands/promo.py:82 #: olgram/commands/promo.py:78 olgram/commands/promo.py:82
msgid "Промокод не найден" msgid "Промокод не найден"
@@ -619,87 +696,102 @@ msgstr ""
" Write your question and we will answer you shortly.\n" " Write your question and we will answer you shortly.\n"
" " " "
#: olgram/utils/permissions.py:40 #: olgram/utils/permissions.py:41
msgid "Владелец бота ограничил доступ к этому функционалу 😞" msgid "Владелец бота ограничил доступ к этому функционалу 😞"
msgstr "The bot owner has restricted access to this functionality 😞" msgstr "The bot owner has restricted access to this functionality 😞"
#: olgram/utils/permissions.py:52 #: olgram/utils/permissions.py:53
msgid "Владелец бота ограничил доступ к этому функционалу😞" msgid "Владелец бота ограничил доступ к этому функционалу😞"
msgstr "The owner of the bot has restricted access to this function😞" msgstr "The owner of the bot has restricted access to this function😞"
#: server/custom.py:55 #: server/custom.py:57
msgid "" msgid ""
"<b>Политика конфиденциальности</b>\n" "<b>Политика конфиденциальности</b>\n"
"\n" "\n"
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При " "Этот бот не хранит ваши сообщения, имя пользователя и @username. При "
"отправке сообщения (кроме команд /start и /security_policy) ваш " "отправке сообщения (кроме команд /start и /security_policy) ваш "
"идентификатор пользователя записывается в кеш на некоторое время и потом " "идентификатор пользователя записывается в кеш на некоторое время и потом "
"удаляется из кеша. Этот идентификатор используется только для общения с " "удаляется из кеша. Этот идентификатор используется для общения с "
"оператором; боты Olgram не делают массовых рассылок.\n" "оператором.\n"
"\n" "\n"
msgstr "" msgstr ""
"<b>Privacy Policy</b>.\n" "<b>Privacy Policy</b>.\n"
"\n" "\n"
"This bot does not store your messages, username and @username. When you send " "This bot does not store your messages, username and @username. When you send "
"a message (except for /start and /security_policy), your username is cached " "a message (except for /start and /security_policy), your username is cached "
"for a while and then deleted from the cache. This ID is only used for " "for a while and then deleted from the cache. This ID is used for "
"communicating with the operator; Olgram bots do not do mass mailings.\n" "communicating with the operator\n"
"\n" "\n"
#: server/custom.py:61 #: server/custom.py:62
msgid "" msgid ""
"При отправке сообщения (кроме команд /start и /security_policy) оператор " "При отправке сообщения (кроме команд /start и /security_policy) оператор "
"<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в " "<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в "
"силу настроек, которые оператор указал при создании бота." "силу настроек, которые оператор указал при создании бота.\n"
"\n"
msgstr "" msgstr ""
"When sending a message (except /start and /security_policy), the operator " "When sending a message (except /start and /security_policy), the operator "
"<b>sees</b> your username, @username and user ID by virtue of the settings " "<b>sees</b> your username, @username and user ID by virtue of the settings "
"that the operator specified when creating the bot." "that the operator specified when creating the bot.\n"
"\n"
#: server/custom.py:65 #: server/custom.py:66
msgid "" msgid ""
"В зависимости от ваших настроек конфиденциальности Telegram, оператор может " "В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
"видеть ваш username, имя пользователя и другую информацию." "видеть ваш username, имя пользователя и другую информацию.\n"
"\n"
msgstr "" msgstr ""
"Depending on your Telegram privacy settings, the operator may see your " "Depending on your Telegram privacy settings, the operator may see your "
"username, username and other information." "username, username and other information.\n"
"\n"
#: server/custom.py:76 #: server/custom.py:70
msgid ""
"В этом боте включена массовая рассылка в силу настроек, которые оператор "
"указал при создании бота. Ваш идентификатор пользователя может быть записан "
"в базу данных на долгое время"
msgstr "Mailing enabled for this bot"
#: server/custom.py:73
msgid "В этом боте нет массовой рассылки сообщений"
msgstr "Mailing disabled for this bot"
#: server/custom.py:83
msgid "Сообщение от пользователя " msgid "Сообщение от пользователя "
msgstr "Message from the user " msgstr "Message from the user "
#: server/custom.py:135 #: server/custom.py:157
msgid "Вы заблокированы в этом боте" msgid "Вы заблокированы в этом боте"
msgstr "You are blocked in this bot" msgstr "You are blocked in this bot"
#: server/custom.py:141 #: server/custom.py:163
msgid "Слишком много сообщений, подождите одну минуту" msgid "Слишком много сообщений, подождите одну минуту"
msgstr "Too many messages, wait one minute" msgstr "Too many messages, wait one minute"
#: server/custom.py:148 #: server/custom.py:170
msgid "Не удаётся связаться с владельцем бота" msgid "Не удаётся связаться с владельцем бота"
msgstr "Cannot contact the owner of the bot" msgstr "Cannot contact the owner of the bot"
#: server/custom.py:179 #: server/custom.py:202
msgid "" msgid ""
"<i>Невозможно переслать сообщение: автор не найден (сообщение слишком " "<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
"старое?)</i>" "старое?)</i>"
msgstr "" msgstr ""
"<i>Cannot forward this message: author not found (message too old?)</i>" "<i>Cannot forward this message: author not found (message too old?)</i>"
#: server/custom.py:187 #: server/custom.py:210
msgid "Пользователь заблокирован" msgid "Пользователь заблокирован"
msgstr "User is blocked" msgstr "User is blocked"
#: server/custom.py:192 #: server/custom.py:215
msgid "Пользователь не был забанен" msgid "Пользователь не был забанен"
msgstr "The user was not banned" msgstr "The user was not banned"
#: server/custom.py:195 #: server/custom.py:218
msgid "Пользователь разбанен" msgid "Пользователь разбанен"
msgstr "A user has been unlocked" msgstr "A user has been unlocked"
#: server/custom.py:200 #: server/custom.py:223
msgid "<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>" msgid "<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"
msgstr "<i>Cannot forward the message (has the author blocked the bot?)</i>" msgstr "<i>Cannot forward the message (has the author blocked the bot?)</i>"

View File

@@ -5,8 +5,8 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"POT-Creation-Date: 2022-09-02 05:07+0400\n" "POT-Creation-Date: 2024-03-02 19:47+0400\n"
"PO-Revision-Date: 2022-09-02 05:12+0400\n" "PO-Revision-Date: 2024-03-02 19:48+0400\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: uk_UA\n" "Language: uk_UA\n"
@@ -16,7 +16,7 @@ msgstr ""
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" "n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 3.1\n" "X-Generator: Poedit 3.4.2\n"
#: olgram/commands/admin.py:21 olgram/commands/info.py:21 #: olgram/commands/admin.py:21 olgram/commands/info.py:21
#: olgram/commands/promo.py:23 olgram/commands/promo.py:39 #: olgram/commands/promo.py:23 olgram/commands/promo.py:39
@@ -63,27 +63,27 @@ msgstr "Скасувати"
msgid "Отправлено" msgid "Отправлено"
msgstr "Надіслано" msgstr "Надіслано"
#: olgram/commands/bot_actions.py:22 #: olgram/commands/bot_actions.py:27
msgid "Бот удалён" msgid "Бот удалён"
msgstr "Бот видалений" msgstr "Бот видалений"
#: olgram/commands/bot_actions.py:38 olgram/commands/bot_actions.py:50 #: olgram/commands/bot_actions.py:49 olgram/commands/bot_actions.py:67
msgid "Текст сброшен" msgid "Текст сброшен"
msgstr "Текст скинутий" msgstr "Текст скинутий"
#: olgram/commands/bot_actions.py:64 #: olgram/commands/bot_actions.py:81
msgid "Выбран личный чат" msgid "Выбран личный чат"
msgstr "Вибраний особистий чат" msgstr "Вибраний особистий чат"
#: olgram/commands/bot_actions.py:77 #: olgram/commands/bot_actions.py:94
msgid "Бот вышел из чатов" msgid "Бот вышел из чатов"
msgstr "Бот вийшов із чатів" msgstr "Бот вийшов із чатів"
#: olgram/commands/bot_actions.py:83 #: olgram/commands/bot_actions.py:100
msgid "Нельзя привязать бота к этому чату" msgid "Нельзя привязать бота к этому чату"
msgstr "Не можна прив'язати робота до цього чату" msgstr "Не можна прив'язати робота до цього чату"
#: olgram/commands/bot_actions.py:87 #: olgram/commands/bot_actions.py:104
msgid "Выбран чат {0}" msgid "Выбран чат {0}"
msgstr "Вибраний чат {0}" msgstr "Вибраний чат {0}"
@@ -198,7 +198,11 @@ msgstr "Вихідних повідомлень у всіх роботів: {0}\
msgid "Промо-кодов выдано: {0}\n" msgid "Промо-кодов выдано: {0}\n"
msgstr "Промо-кодів видано: {0}\n" msgstr "Промо-кодів видано: {0}\n"
#: olgram/commands/menu.py:31 #: olgram/commands/info.py:40
msgid "Рекламную плашку выключили: {0}\n"
msgstr ""
#: olgram/commands/menu.py:33
msgid "" msgid ""
"\n" "\n"
" У вас нет добавленных ботов.\n" " У вас нет добавленных ботов.\n"
@@ -213,25 +217,25 @@ msgstr ""
" \n" " \n"
" " " "
#: olgram/commands/menu.py:46 #: olgram/commands/menu.py:48
msgid "Ваши боты" msgid "Ваши боты"
msgstr "Ваші боти" msgstr "Ваші боти"
#: olgram/commands/menu.py:67 #: olgram/commands/menu.py:69
msgid "Личные сообщения" msgid "Личные сообщения"
msgstr "Особисті повідомлення" msgstr "Особисті повідомлення"
#: olgram/commands/menu.py:72 #: olgram/commands/menu.py:74
msgid "❗️ Выйти из всех чатов" msgid "❗️ Выйти из всех чатов"
msgstr "❗️ Вийти зі всіх чатів" msgstr "❗️ Вийти зі всіх чатів"
#: olgram/commands/menu.py:77 olgram/commands/menu.py:122 #: olgram/commands/menu.py:79 olgram/commands/menu.py:124
#: olgram/commands/menu.py:148 olgram/commands/menu.py:184 #: olgram/commands/menu.py:156 olgram/commands/menu.py:209
#: olgram/commands/menu.py:247 #: olgram/commands/menu.py:390
msgid "<< Назад" msgid "<< Назад"
msgstr "<< Назад" msgstr "<< Назад"
#: olgram/commands/menu.py:83 #: olgram/commands/menu.py:85
msgid "" msgid ""
"\n" "\n"
" Этот бот не добавлен в чаты, поэтому все сообщения будут приходить " " Этот бот не добавлен в чаты, поэтому все сообщения будут приходить "
@@ -254,7 +258,7 @@ msgstr ""
" \n" " \n"
" " " "
#: olgram/commands/menu.py:90 #: olgram/commands/menu.py:92
msgid "" msgid ""
"\n" "\n"
" В этом разделе вы можете привязать бота @{0} к чату.\n" " В этом разделе вы можете привязать бота @{0} к чату.\n"
@@ -267,27 +271,31 @@ msgstr ""
" \n" " \n"
" " " "
#: olgram/commands/menu.py:102 #: olgram/commands/menu.py:104
msgid "Текст" msgid "Текст"
msgstr "Текст" msgstr "Текст"
#: olgram/commands/menu.py:107 #: olgram/commands/menu.py:109
msgid "Чат" msgid "Чат"
msgstr "Чат" msgstr "Чат"
#: olgram/commands/menu.py:112 #: olgram/commands/menu.py:114
msgid "Удалить бот" msgid "Удалить бот"
msgstr "Видалити бот" msgstr "Видалити бот"
#: olgram/commands/menu.py:117 #: olgram/commands/menu.py:119
msgid "Статистика" msgid "Статистика"
msgstr "Статистика" msgstr "Статистика"
#: olgram/commands/menu.py:126 #: olgram/commands/menu.py:128
msgid "Опции" msgid "Опции"
msgstr "Опції" msgstr "Опції"
#: olgram/commands/menu.py:131 #: olgram/commands/menu.py:134 olgram/commands/menu.py:190
msgid "Рассылка"
msgstr ""
#: olgram/commands/menu.py:139
msgid "" msgid ""
"\n" "\n"
" Управление ботом @{0}.\n" " Управление ботом @{0}.\n"
@@ -305,11 +313,11 @@ msgstr ""
" @civsocit_feedback_bot\n" " @civsocit_feedback_bot\n"
" " " "
#: olgram/commands/menu.py:143 #: olgram/commands/menu.py:151
msgid "Да, удалить бот" msgid "Да, удалить бот"
msgstr "Так, видалити бот" msgstr "Так, видалити бот"
#: olgram/commands/menu.py:152 #: olgram/commands/menu.py:160
msgid "" msgid ""
"\n" "\n"
" Вы уверены, что хотите удалить бота @{0}?\n" " Вы уверены, что хотите удалить бота @{0}?\n"
@@ -319,43 +327,75 @@ msgstr ""
" Ви впевнені, що хочете видалити бота @{0}?\n" " Ви впевнені, що хочете видалити бота @{0}?\n"
" " " "
#: olgram/commands/menu.py:161 #: olgram/commands/menu.py:169
msgid "Потоки сообщений" msgid "Потоки сообщений"
msgstr "Потоки повідомлень" msgstr "Потоки повідомлень"
#: olgram/commands/menu.py:166 #: olgram/commands/menu.py:174
msgid "Данные пользователя" msgid "Данные пользователя"
msgstr "Дані користувача" msgstr "Дані користувача"
#: olgram/commands/menu.py:171 #: olgram/commands/menu.py:179
msgid "Антифлуд" msgid "Антифлуд"
msgstr "Антифлуд" msgstr "Антифлуд"
#: olgram/commands/menu.py:178 #: olgram/commands/menu.py:184
#, fuzzy
#| msgid "Автоответчик"
msgid "Автоответчик всегда"
msgstr "Автовідповідач"
#: olgram/commands/menu.py:195
msgid "Прерывать поток"
msgstr ""
#: olgram/commands/menu.py:203
msgid "Olgram подпись" msgid "Olgram подпись"
msgstr "Olgram підпис" msgstr "Olgram підпис"
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190 #: olgram/commands/menu.py:214 olgram/commands/menu.py:215
msgid "включены" msgid "включены"
msgstr "включені" msgstr "включені"
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190 #: olgram/commands/menu.py:214 olgram/commands/menu.py:215
msgid "выключены" msgid "выключены"
msgstr "вимкнені" msgstr "вимкнені"
#: olgram/commands/menu.py:191 #: olgram/commands/menu.py:216
#, fuzzy #, fuzzy
#| msgid "включены" #| msgid "включены"
msgid "включен" msgid "включен"
msgstr "включені" msgstr "включені"
#: olgram/commands/menu.py:191 #: olgram/commands/menu.py:216 olgram/commands/menu.py:217
#, fuzzy #, fuzzy
#| msgid "выключены" #| msgid "выключены"
msgid "выключен" msgid "выключен"
msgstr "вимкнені" msgstr "вимкнені"
#: olgram/commands/menu.py:192 #: olgram/commands/menu.py:217
#, fuzzy
#| msgid "включены"
msgid "включён"
msgstr "включені"
#: olgram/commands/menu.py:218
msgid "да"
msgstr "Ча"
#: olgram/commands/menu.py:218
msgid "нет"
msgstr ""
#: olgram/commands/menu.py:219 olgram/commands/menu.py:231
msgid "включена"
msgstr "включена"
#: olgram/commands/menu.py:219 olgram/commands/menu.py:231
msgid "выключена"
msgstr "вимкнена"
#: olgram/commands/menu.py:220
msgid "" msgid ""
"\n" "\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options." " <a href=\"https://olgram.readthedocs.io/ru/latest/options."
@@ -364,69 +404,81 @@ msgid ""
"info\">Данные пользователя</a>: <b>{1}</b>\n" "info\">Данные пользователя</a>: <b>{1}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options." " <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#antiflood\">Антифлуд</a>: <b>{2}</b>\n" "html#antiflood\">Антифлуд</a>: <b>{2}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#always_second_message\">Автоответчик всегда</a>: <b>{3}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#thread_interrupt\">Прерывать поток</a>: <b>{4}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#mailing\">Рассылка</a>: <b>{5}</b>\n"
" " " "
msgstr "" msgstr ""
"\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#threads\">Потоки повідомлень</a>: <b>{0}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-"
"info\">Дані користувача</a>: <b>{1}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#antiflood\">Anti-flood</a>: <b>{2}</b>"
#: olgram/commands/menu.py:199 #: olgram/commands/menu.py:232
msgid "включена"
msgstr "включена"
#: olgram/commands/menu.py:199
msgid "выключена"
msgstr "вимкнена"
#: olgram/commands/menu.py:200
msgid "Olgram подпись: <b>{0}</b>" msgid "Olgram подпись: <b>{0}</b>"
msgstr "Olgram підпис: <b>{0}</b>" msgstr "Olgram підпис: <b>{0}</b>"
#: olgram/commands/menu.py:210 olgram/commands/menu.py:272 #: olgram/commands/menu.py:259 olgram/commands/menu.py:421
#: olgram/commands/menu.py:314 #: olgram/commands/menu.py:480
msgid "<< Завершить редактирование" msgid "<< Завершить редактирование"
msgstr "<< Завершити редагування" msgstr "<< Завершити редагування"
#: olgram/commands/menu.py:214 #: olgram/commands/menu.py:263
msgid "Автоответчик" msgid "Автоответчик"
msgstr "Автовідповідач" msgstr "Автовідповідач"
#: olgram/commands/menu.py:219 olgram/commands/menu.py:286 #: olgram/commands/menu.py:268 olgram/commands/menu.py:435
msgid "Сбросить текст" msgid "Сбросить текст"
msgstr "Скинути текст" msgstr "Скинути текст"
#: olgram/commands/menu.py:224 #: olgram/commands/menu.py:273 olgram/commands/menu.py:440
msgid "[все языки]"
msgstr ""
#: olgram/commands/menu.py:290
msgid "" msgid ""
"\n" "\n"
" Сейчас вы редактируете текст, который отправляется после того, как " " Сейчас вы редактируете текст, который отправляется после того, как "
"пользователь отправит вашему боту @{0}\n" "пользователь отправит вашему боту @{0}\n"
" команду /start\n" " команду /start\n"
"\n" "\n"
" Текущий текст:\n" " Текущий текст{2}:\n"
" <pre>\n" " <pre>{1}</pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n" " Отправьте сообщение, чтобы изменить текст.\n"
" " " "
msgstr "" msgstr ""
"\n"
" Зараз ви редагуєте текст, який надсилається після того, як користувач "
"надішле вашому боту @{0}\n"
" команду /start\n"
"\n"
" Поточний текст:\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Надішліть повідомлення, щоб змінити текст.\n"
" \n"
" "
#: olgram/commands/menu.py:251 #: olgram/commands/menu.py:300 olgram/commands/menu.py:467
msgid " (для языка {0})"
msgstr ""
#: olgram/commands/menu.py:313
#, fuzzy
#| msgid "Отменить"
msgid "<< Отменить рассылку"
msgstr "Скасувати"
#: olgram/commands/menu.py:317
msgid ""
"\n"
" Напишите сообщение, которое нужно разослать всем подписчикам вашего бота "
"@{0}. \n"
" У сообщения будет до {1} получателей. \n"
" Учтите, что\n"
" 1. Рассылается только одно сообщение за раз (в т.ч. только одна "
"картинка)\n"
" 2. Когда рассылка запущена, её нельзя отменить \n"
" "
msgstr ""
#: olgram/commands/menu.py:367
msgid "Не удалось загрузить файл (слишком большой размер?)"
msgstr ""
#: olgram/commands/menu.py:374
msgid "Да, начать рассылку"
msgstr ""
#: olgram/commands/menu.py:394
msgid "" msgid ""
"\n" "\n"
" Статистика по боту @{0}\n" " Статистика по боту @{0}\n"
@@ -446,45 +498,32 @@ msgstr ""
" Забанено користувачів: <b>{4}</b>\n" " Забанено користувачів: <b>{4}</b>\n"
" " " "
#: olgram/commands/menu.py:276 #: olgram/commands/menu.py:425
msgid "Предыдущий текст" msgid "Предыдущий текст"
msgstr "Попередній текст" msgstr "Попередній текст"
#: olgram/commands/menu.py:281 #: olgram/commands/menu.py:430
msgid "Шаблоны ответов..." msgid "Шаблоны ответов..."
msgstr "Шаблони відповідей..." msgstr "Шаблони відповідей..."
#: olgram/commands/menu.py:291 #: olgram/commands/menu.py:457
msgid "" msgid ""
"\n" "\n"
" Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в " " Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в "
"ответ на все входящие сообщения @{0} автоматически. По умолчанию оно " "ответ на все входящие сообщения @{0} автоматически. По умолчанию оно "
"отключено.\n" "отключено.\n"
"\n" "\n"
" Текущий текст:\n" " Текущий текст{2}:\n"
" <pre>\n" " <pre>{1}</pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n" " Отправьте сообщение, чтобы изменить текст.\n"
" " " "
msgstr "" msgstr ""
"\n"
" Зараз ви редагуєте текст автовідповідача. Це повідомлення надсилається у "
"відповідь на всі вхідні повідомлення @{0} автоматично. За замовчуванням його "
"вимкнено.\n"
"\n"
" Поточний текст:\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Надішліть повідомлення, щоб змінити текст.\n"
" "
#: olgram/commands/menu.py:301 #: olgram/commands/menu.py:466
msgid "(отключено)" msgid "отключено"
msgstr "(відключено)" msgstr "відключено"
#: olgram/commands/menu.py:318 #: olgram/commands/menu.py:484
msgid "" msgid ""
"\n" "\n"
" Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n" " Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n"
@@ -510,30 +549,50 @@ msgstr ""
" \n" " \n"
" " " "
#: olgram/commands/menu.py:337 #: olgram/commands/menu.py:503
msgid "(нет шаблонов)" msgid "(нет шаблонов)"
msgstr "(Немає шаблонів)" msgstr "(Немає шаблонів)"
#: olgram/commands/menu.py:376 #: olgram/commands/menu.py:565
msgid "У вас нет шаблонов, чтобы их удалять" msgid "У вас нет шаблонов, чтобы их удалять"
msgstr "У вас немає шаблонів, щоб їх видаляти" msgstr "У вас немає шаблонів, щоб їх видаляти"
#: olgram/commands/menu.py:378 #: olgram/commands/menu.py:567
msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}" msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}"
msgstr "Неправильне число. Щоб видалити шаблон, введіть число від 0 до {0}" msgstr "Неправильне число. Щоб видалити шаблон, введіть число від 0 до {0}"
#: olgram/commands/menu.py:386 #: olgram/commands/menu.py:575
msgid "У вашего бота уже слишком много шаблонов" msgid "У вашего бота уже слишком много шаблонов"
msgstr "У вашого бота вже дуже багато шаблонів" msgstr "У вашого бота вже дуже багато шаблонів"
#: olgram/commands/menu.py:390 #: olgram/commands/menu.py:579
msgid "Такой текст уже есть в списке шаблонов" msgid "Такой текст уже есть в списке шаблонов"
msgstr "Такий текст вже є у списку шаблонів" msgstr "Такий текст вже є у списку шаблонів"
#: olgram/commands/menu.py:408 #: olgram/commands/menu.py:597
msgid "У вас нет прав на этого бота" msgid "У вас нет прав на этого бота"
msgstr "У вас немає прав на цього бота" msgstr "У вас немає прав на цього бота"
#: olgram/commands/menu.py:617 olgram/commands/menu.py:643
msgid "Рассылка была совсем недавно, подождите немного"
msgstr ""
#: olgram/commands/menu.py:619 olgram/commands/menu.py:645
msgid "Нет пользователей для рассылки"
msgstr ""
#: olgram/commands/menu.py:647
msgid "Рассылка запущена"
msgstr "Розсилка запущена"
#: olgram/commands/menu.py:649
msgid "Рассылка завершена, отправлено {0} сообщений"
msgstr "Розсилка завершена, надіслано {0} повідомлень"
#: olgram/commands/menu.py:651
msgid "Устарело, создайте новую рассылку"
msgstr "Застаріло, створіть нову розсилку"
#: olgram/commands/promo.py:27 #: olgram/commands/promo.py:27
msgid "" msgid ""
"Новый промокод\n" "Новый промокод\n"
@@ -629,23 +688,23 @@ msgstr ""
" \n" " \n"
" " " "
#: olgram/utils/permissions.py:40 #: olgram/utils/permissions.py:41
msgid "Владелец бота ограничил доступ к этому функционалу 😞" msgid "Владелец бота ограничил доступ к этому функционалу 😞"
msgstr "Власник бота обмежив доступ до цього функціоналу 😞" msgstr "Власник бота обмежив доступ до цього функціоналу 😞"
#: olgram/utils/permissions.py:52 #: olgram/utils/permissions.py:53
msgid "Владелец бота ограничил доступ к этому функционалу😞" msgid "Владелец бота ограничил доступ к этому функционалу😞"
msgstr "Власник бота обмежив доступ до цього функціоналу 😞" msgstr "Власник бота обмежив доступ до цього функціоналу 😞"
#: server/custom.py:55 #: server/custom.py:57
msgid "" msgid ""
"<b>Политика конфиденциальности</b>\n" "<b>Политика конфиденциальности</b>\n"
"\n" "\n"
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При " "Этот бот не хранит ваши сообщения, имя пользователя и @username. При "
"отправке сообщения (кроме команд /start и /security_policy) ваш " "отправке сообщения (кроме команд /start и /security_policy) ваш "
"идентификатор пользователя записывается в кеш на некоторое время и потом " "идентификатор пользователя записывается в кеш на некоторое время и потом "
"удаляется из кеша. Этот идентификатор используется только для общения с " "удаляется из кеша. Этот идентификатор используется для общения с "
"оператором; боты Olgram не делают массовых рассылок.\n" "оператором.\n"
"\n" "\n"
msgstr "" msgstr ""
"<b>Політика конфіденційності</b>\n" "<b>Політика конфіденційності</b>\n"
@@ -653,45 +712,63 @@ msgstr ""
"Цей бот не зберігає ваші повідомлення, ім'я користувача та @username. При " "Цей бот не зберігає ваші повідомлення, ім'я користувача та @username. При "
"надсиланні повідомлення (крім команд /start та /security_policy) ваш " "надсиланні повідомлення (крім команд /start та /security_policy) ваш "
"ідентифікатор користувача записується в кеш на деякий час і потім " "ідентифікатор користувача записується в кеш на деякий час і потім "
"видаляється з кеша. Цей ідентифікатор використовується лише для спілкування " "видаляється з кеша. Цей ідентифікатор використовується для спілкування з "
"з оператором; боти Olgram не роблять масових розсилок.\n" "оператором.\n"
"\n" "\n"
#: server/custom.py:61 #: server/custom.py:62
msgid "" msgid ""
"При отправке сообщения (кроме команд /start и /security_policy) оператор " "При отправке сообщения (кроме команд /start и /security_policy) оператор "
"<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в " "<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в "
"силу настроек, которые оператор указал при создании бота." "силу настроек, которые оператор указал при создании бота.\n"
"\n"
msgstr "" msgstr ""
"При надсиланні повідомлення (крім команд /start та /security_policy) " "При надсиланні повідомлення (крім команд /start та /security_policy) "
"оператор <b>бачить</b> ваше ім'я користувача, @username та ідентифікатор " "оператор <b>бачить</b> ваше ім'я користувача, @username та ідентифікатор "
"користувача через налаштування, які оператор вказав при створенні бота." "користувача через налаштування, які оператор вказав при створенні бота.\n"
"\n"
#: server/custom.py:65 #: server/custom.py:66
msgid "" msgid ""
"В зависимости от ваших настроек конфиденциальности Telegram, оператор может " "В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
"видеть ваш username, имя пользователя и другую информацию." "видеть ваш username, имя пользователя и другую информацию.\n"
"\n"
msgstr "" msgstr ""
"Залежно від ваших налаштувань конфіденційності Telegram оператор може бачити " "Залежно від ваших налаштувань конфіденційності Telegram оператор може бачити "
"ваш username, ім'я користувача та іншу інформацію." "ваш username, ім'я користувача та іншу інформацію.\n"
"\n"
#: server/custom.py:76 #: server/custom.py:70
msgid ""
"В этом боте включена массовая рассылка в силу настроек, которые оператор "
"указал при создании бота. Ваш идентификатор пользователя может быть записан "
"в базу данных на долгое время"
msgstr ""
"У цьому роботі включено масове розсилання в силу налаштувань, які оператор "
"вказав при створенні робота. Ваш ідентифікатор користувача може бути "
"записаний до бази даних на довгий час"
#: server/custom.py:73
msgid "В этом боте нет массовой рассылки сообщений"
msgstr "У цьому роботі немає масової розсилки повідомлень"
#: server/custom.py:83
msgid "Сообщение от пользователя " msgid "Сообщение от пользователя "
msgstr "Допис від користувача " msgstr "Допис від користувача "
#: server/custom.py:135 #: server/custom.py:157
msgid "Вы заблокированы в этом боте" msgid "Вы заблокированы в этом боте"
msgstr "Ви заблоковані у цьому боті" msgstr "Ви заблоковані у цьому боті"
#: server/custom.py:141 #: server/custom.py:163
msgid "Слишком много сообщений, подождите одну минуту" msgid "Слишком много сообщений, подождите одну минуту"
msgstr "Забагато повідомлень, зачекайте одну хвилину" msgstr "Забагато повідомлень, зачекайте одну хвилину"
#: server/custom.py:148 #: server/custom.py:170
msgid "Не удаётся связаться с владельцем бота" msgid "Не удаётся связаться с владельцем бота"
msgstr "Не вдається зв'язатися з власником бота" msgstr "Не вдається зв'язатися з власником бота"
#: server/custom.py:179 #: server/custom.py:202
msgid "" msgid ""
"<i>Невозможно переслать сообщение: автор не найден (сообщение слишком " "<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
"старое?)</i>" "старое?)</i>"
@@ -699,19 +776,19 @@ msgstr ""
"<i>Неможливо надіслати повідомлення: автора не знайдено (повідомлення " "<i>Неможливо надіслати повідомлення: автора не знайдено (повідомлення "
"занадто старе?)</i>" "занадто старе?)</i>"
#: server/custom.py:187 #: server/custom.py:210
msgid "Пользователь заблокирован" msgid "Пользователь заблокирован"
msgstr "Користувач заблоковано" msgstr "Користувач заблоковано"
#: server/custom.py:192 #: server/custom.py:215
msgid "Пользователь не был забанен" msgid "Пользователь не был забанен"
msgstr "Користувач не був забанений" msgstr "Користувач не був забанений"
#: server/custom.py:195 #: server/custom.py:218
msgid "Пользователь разбанен" msgid "Пользователь разбанен"
msgstr "Користувач розбанений" msgstr "Користувач розбанений"
#: server/custom.py:200 #: server/custom.py:223
msgid "<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>" msgid "<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"
msgstr "<i>Неможливо надіслати повідомлення (автор заблокував робота?)</i>" msgstr "<i>Неможливо надіслати повідомлення (автор заблокував робота?)</i>"

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

@@ -7,9 +7,9 @@ from olgram.settings import TORTOISE_ORM, OlgramSettings
from olgram.utils.permissions import AccessMiddleware from olgram.utils.permissions import AccessMiddleware
from server.custom import init_redis from server.custom import init_redis
import olgram.commands.menu # noqa: F401
import olgram.commands.bots # noqa: F401 import olgram.commands.bots # noqa: F401
import olgram.commands.start # noqa: F401 import olgram.commands.start # noqa: F401
import olgram.commands.menu # noqa: F401
import olgram.commands.bot_actions # noqa: F401 import olgram.commands.bot_actions # noqa: F401
import olgram.commands.info # noqa: F401 import olgram.commands.info # noqa: F401
import olgram.commands.promo # noqa: F401 import olgram.commands.promo # noqa: F401

View File

@@ -1,13 +1,15 @@
""" """
Здесь работа с конкретным ботом Здесь работа с конкретным ботом
""" """
import logging
from aiogram import types
from asyncio import sleep from asyncio import sleep
from datetime import datetime from datetime import datetime
from aiogram import types from olgram.utils.mix import send_stored_message
from aiogram.utils import exceptions from aiogram.utils import exceptions
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 _
@@ -29,27 +31,39 @@ async def delete_bot(bot: Bot, call: types.CallbackQuery):
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(_("Текст сброшен"))
@@ -100,6 +114,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
@@ -116,28 +140,36 @@ async def mailing(bot: Bot, call: types.CallbackQuery):
await bot.save(update_fields=["enable_mailing"]) await bot.save(update_fields=["enable_mailing"])
async def go_mailing(bot: Bot, context) -> int: async def tags(bot: Bot, call: types.CallbackQuery):
bot.enable_tags = not bot.enable_tags
await bot.save(update_fields=["enable_tags"])
async def go_mailing(bot: Bot, context: dict) -> int:
users = await bot.mailing_users users = await bot.mailing_users
a_bot = AioBot(bot.decrypted_token()) a_bot = AioBot(bot.decrypted_token())
count = 0 count = 0
print(f"start mailing {context}")
for user in users: for user in users:
bot.last_mailing_at = datetime.now() bot.last_mailing_at = datetime.now()
await bot.save(update_fields=["last_mailing_at"]) await bot.save(update_fields=["last_mailing_at"])
try: try:
await sleep(0.05) await sleep(0.05)
try: try:
await send_stored_message(context, a_bot, user.telegram_id) file_id = await send_stored_message(context, a_bot, user.telegram_id)
except exceptions.RetryAfter as err: except exceptions.RetryAfter as err:
await sleep(err.timeout) await sleep(err.timeout)
await send_stored_message(context, a_bot, user.telegram_id) file_id = await send_stored_message(context, a_bot, user.telegram_id)
if file_id:
context["mailing_id"] = file_id
count += 1 count += 1
except (exceptions.ChatNotFound, exceptions.BotBlocked, exceptions.UserDeactivated): except (exceptions.ChatNotFound, exceptions.BotBlocked, exceptions.UserDeactivated):
await user.delete() await user.delete()
except exceptions.TelegramAPIError: except Exception as err:
logging.error("mailing error")
logging.error(err, exc_info=True)
pass pass
return count return count

View File

@@ -1,10 +1,11 @@
import logging
from io import BytesIO
from olgram.router import dp from olgram.router import dp
from datetime import datetime, timedelta, timezone
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, send_stored_message
from olgram.commands import bot_actions from olgram.commands import bot_actions
@@ -12,6 +13,7 @@ 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"
@@ -178,11 +180,29 @@ 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="always_second_message",
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="mailing",
chat=empty)) chat=empty))
) )
keyboard.insert(
types.InlineKeyboardButton(text=_("Прерывать поток"),
callback_data=menu_callback.new(level=3, bot_id=bot.id,
operation="thread_interrupt",
chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text=_("Теги"),
callback_data=menu_callback.new(level=3, bot_id=bot.id,
operation="tags",
chat=empty))
)
is_promo = await bot.is_promo() is_promo = await bot.is_promo()
if is_promo: if is_promo:
keyboard.insert( keyboard.insert(
@@ -200,13 +220,20 @@ 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 _("выключен")
enable_always_second_message = _("включён") if bot.enable_always_second_message else _("выключен")
thread_interrupt = _("да") if bot.enable_thread_interrupt else _("нет")
mailing_turn = _("включена") if bot.enable_mailing else _("выключена") mailing_turn = _("включена") if bot.enable_mailing else _("выключена")
tags_turn = _("включены") if bot.enable_tags 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}</b>
""")).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>
<a href="https://olgram.readthedocs.io/ru/latest/options.html#mailing">Рассылка</a>: <b>{5}</b>
Теги: <b>{6}</b>
""")).format(thread_turn, info_turn, antiflood_turn, enable_always_second_message, thread_interrupt,
mailing_turn, tags_turn)
if is_promo: if is_promo:
olgram_turn = _("включена") if bot.enable_olgram_text else _("выключена") olgram_turn = _("включена") if bot.enable_olgram_text else _("выключена")
@@ -215,9 +242,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,23 +272,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,
prepared_languages.get(lang, bot.start_text),
_(" (для языка {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:
@@ -275,6 +336,65 @@ async def send_bot_mailing_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")
@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
buffer = BytesIO()
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 in (types.ContentType.PHOTO, types.ContentType.DOCUMENT, types.ContentType.AUDIO,
types.ContentType.VIDEO):
proxy["mailing_caption"] = message.caption
if message.content_type == types.ContentType.PHOTO:
obj = message.photo[-1]
elif message.content_type == types.ContentType.DOCUMENT:
obj = message.document
elif message.content_type == types.ContentType.AUDIO:
obj = message.audio
elif message.content_type == types.ContentType.VIDEO:
obj = message.video
if obj.file_size and obj.file_size > 4194304:
return await message.answer(_("Слишком большой файл (4 Мб максимум)"))
try:
await obj.download(buffer, timeout=5)
except Exception as err:
logging.error("Error downloading file")
logging.error(err, exc_info=True)
return await message.answer(_("Не удалось загрузить файл (слишком большой размер?)"))
proxy["mailing_data"] = buffer.getvalue()
proxy["mailing_file_name"] = getattr(obj, "file_name", None)
_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=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="go_go_mailing",
chat=empty))
)
await AioBot.get_current().send_message(message.chat.id, reply_to_message_id=_message_id,
text="Вы уверены, что хотите разослать это сообщение всем пользователям?",
reply_markup=keyboard)
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:
@@ -301,9 +421,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 +450,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 +526,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
@@ -504,7 +627,7 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
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 operation == "go_mailing":
if bot.last_mailing_at and bot.last_mailing_at >= datetime.now() - timedelta(minutes=5): if bot.last_mailing_at and bot.last_mailing_at >= datetime.now(tz=timezone.utc) - timedelta(minutes=5):
return await call.answer(_("Рассылка была совсем недавно, подождите немного"), show_alert=True) return await call.answer(_("Рассылка была совсем недавно, подождите немного"), show_alert=True)
if not await bot.mailing_users: if not await bot.mailing_users:
return await call.answer(_("Нет пользователей для рассылки")) return await call.answer(_("Нет пользователей для рассылки"))
@@ -516,11 +639,30 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
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":
return await bot_actions.delete_bot(bot, call) return await bot_actions.delete_bot(bot, call)
if operation == "mailing":
await bot_actions.mailing(bot, call)
return await send_bot_settings_menu(bot, call)
if operation == "go_go_mailing":
if (await state.get_state()) == "wait_mailing_text":
async with state.proxy() as proxy:
mailing_data = dict(proxy)
await state.reset_state()
if bot.last_mailing_at and bot.last_mailing_at >= datetime.now(tz=timezone.utc) - 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)
return await call.message.answer(_("Рассылка завершена, отправлено {0} сообщений").format(count))
else:
return await call.answer(_("Устарело, создайте новую рассылку"))
if operation == "chat": if operation == "chat":
return await bot_actions.select_chat(bot, call, callback_data.get("chat")) return await bot_actions.select_chat(bot, call, callback_data.get("chat"))
if operation == "threads": if operation == "threads":
@@ -529,40 +671,47 @@ 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 == "additional_info":
await bot_actions.additional_info(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 == "mailing": if operation == "mailing":
await bot_actions.mailing(bot, call) await bot_actions.mailing(bot, call)
return await send_bot_settings_menu(bot, call) return await send_bot_settings_menu(bot, call)
if operation == "additional_info": if operation == "tags":
await bot_actions.additional_info(bot, call) await bot_actions.tags(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) 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

@@ -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

@@ -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";

View File

@@ -1,14 +1,10 @@
-- upgrade -- -- 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" ( CREATE TABLE IF NOT EXISTS "mailinguser" (
"id" BIGSERIAL NOT NULL PRIMARY KEY, "id" BIGSERIAL NOT NULL PRIMARY KEY,
"telegram_id" BIGINT NOT NULL, "telegram_id" BIGINT NOT NULL,
"bot_id" INT NOT NULL REFERENCES "bot" ("id") ON DELETE CASCADE, "bot_id" INT NOT NULL REFERENCES "bot" ("id") ON DELETE CASCADE,
CONSTRAINT "uid_mailinguser_bot_id_906a76" UNIQUE ("bot_id", "telegram_id") CONSTRAINT "uid_mailinguser_bot_id_906a76" UNIQUE ("bot_id", "telegram_id")
); );
CREATE INDEX IF NOT EXISTS "idx_mailinguser_telegra_55de60" ON "mailinguser" ("telegram_id");; CREATE INDEX IF NOT EXISTS "idx_mailinguser_telegra_55de60" ON "mailinguser" ("telegram_id");
-- downgrade -- -- downgrade --
ALTER TABLE "bot" DROP COLUMN "last_mailing_at";
ALTER TABLE "bot" DROP COLUMN "enable_mailing";
DROP TABLE IF EXISTS "mailinguser"; DROP TABLE IF EXISTS "mailinguser";

View File

@@ -0,0 +1,4 @@
-- upgrade --
ALTER TABLE "bot_start_message" ALTER COLUMN "locale" TYPE VARCHAR(15) USING "locale"::VARCHAR(15);
-- downgrade --
ALTER TABLE "bot_start_message" ALTER COLUMN "locale" TYPE VARCHAR(5) USING "locale"::VARCHAR(5);

View File

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

View File

@@ -46,7 +46,10 @@ 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_always_second_message = fields.BooleanField(default=False)
enable_thread_interrupt = fields.BooleanField(default=True)
enable_mailing = fields.BooleanField(default=False) enable_mailing = fields.BooleanField(default=False)
enable_tags = fields.BooleanField(default=False)
last_mailing_at = fields.DatetimeField(null=True, default=None) last_mailing_at = fields.DatetimeField(null=True, default=None)
def decrypted_token(self): def decrypted_token(self):
@@ -72,15 +75,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=15)
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):
@@ -95,6 +109,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)

View File

@@ -7,7 +7,6 @@ from datetime import timedelta
import typing as ty import typing as ty
from olgram.utils.crypto import Cryptor from olgram.utils.crypto import Cryptor
load_dotenv() load_dotenv()
@@ -41,7 +40,7 @@ class OlgramSettings(AbstractSettings):
@classmethod @classmethod
def version(cls): def version(cls):
return "0.5.0" return "0.7.3"
@classmethod @classmethod
@lru_cache @lru_cache
@@ -161,4 +160,6 @@ TORTOISE_ORM = {
"default_connection": "default", "default_connection": "default",
}, },
}, },
"use_tz": False,
"timezone": "UTC"
} }

View File

@@ -1,6 +1,8 @@
import logging
from io import BytesIO
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 aiogram import types, Bot as AioBot
from typing import Optional from typing import Optional
@@ -39,15 +41,23 @@ async def send_stored_message(storage: dict, bot: AioBot, chat_id: int):
return await bot.send_message(chat_id, storage["mailing_text"], parse_mode="HTML") return await bot.send_message(chat_id, storage["mailing_text"], parse_mode="HTML")
if content_type == types.ContentType.LOCATION: if content_type == types.ContentType.LOCATION:
return await bot.send_location(chat_id, storage["mailing_location"][0], storage["mailing_location"][1]) return await bot.send_location(chat_id, storage["mailing_location"][0], storage["mailing_location"][1])
if content_type == types.ContentType.AUDIO: if content_type in (types.ContentType.AUDIO, types.ContentType.VIDEO, types.ContentType.DOCUMENT,
return await bot.send_audio(chat_id, audio=storage["mailing_audio"], caption=storage.get("mailing_caption")) types.ContentType.PHOTO):
if content_type == types.ContentType.DOCUMENT: caption = storage.get("mailing_caption")
return await bot.send_document(chat_id, document=storage["mailing_document"], if storage.get("mailing_id"):
caption=storage.get("mailing_caption")) logging.info("Mailing use file id")
if content_type == types.ContentType.PHOTO: obj = storage["mailing_id"]
return await bot.send_photo(chat_id, photo=storage["mailing_photo"], else:
caption=storage.get("mailing_caption")) logging.info("Mailing upload file")
if content_type == types.ContentType.VIDEO: obj = types.InputFile(BytesIO(storage["mailing_data"]), filename=storage.get("mailing_file_name"))
return await bot.send_video(chat_id, video=storage["mailing_video"],
caption=storage.get("mailing_caption")) if content_type == types.ContentType.AUDIO:
raise NotImplementedError("Mailing, unknown content type") return (await bot.send_audio(chat_id, audio=obj, caption=caption)).audio.file_id
if content_type == types.ContentType.PHOTO:
return (await bot.send_photo(chat_id, photo=obj, caption=caption)).photo[-1].file_id
if content_type == types.ContentType.VIDEO:
return (await bot.send_video(chat_id, video=obj, caption=caption)).video.file_id
if content_type == types.ContentType.DOCUMENT:
return (await bot.send_document(chat_id, document=obj, caption=caption)).document.file_id
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

@@ -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, MailingUser 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
@@ -38,7 +40,11 @@ def _message_unique_id(bot_id: int, message_id: int) -> str:
return f"{bot_id}_{message_id}" return f"{bot_id}_{message_id}"
def _thread_uniqie_id(bot_id: int, chat_id: int) -> str: def _tag_uid(bot_id: int, user_id: int) -> str:
return f"tag_{bot_id}_{user_id}"
def _thread_unique_id(bot_id: int, chat_id: int) -> str:
return f"thread_{bot_id}_{chat_id}" return f"thread_{bot_id}_{chat_id}"
@@ -75,7 +81,7 @@ def _on_security_policy(message: types.Message, bot):
parse_mode="HTML") parse_mode="HTML")
async def send_user_message(message: types.Message, super_chat_id: int, bot): async def send_user_message(message: types.Message, super_chat_id: int, bot, tag: str = ""):
"""Переслать сообщение от пользователя, добавлять к нему user info при необходимости""" """Переслать сообщение от пользователя, добавлять к нему user info при необходимости"""
if bot.enable_additional_info: if bot.enable_additional_info:
user_info = _("Сообщение от пользователя ") user_info = _("Сообщение от пользователя ")
@@ -83,49 +89,94 @@ 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}"
tag = await _redis.get(_tag_uid(bot.pk, message.from_user.id), encoding="utf-8")
if tag:
user_info += f" | tag: {tag}"
# Добавлять информацию в конец текста # Добавлять информацию в конец текста
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, elif tag:
pexpire=ServerSettings.redis_timeout_ms()) # добавлять тег в конец текста
return new_message if message.content_type == types.ContentType.TEXT and len(message.text) + len(tag) < 4093:
new_message = await message.bot.send_message(super_chat_id, message.text + "\n\n" + tag)
else:
new_message = await message.bot.send_message(super_chat_id, text=tag)
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,
pexpire=ServerSettings.redis_timeout_ms())
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 bot.enable_tags:
tag = await _redis.get(_tag_uid(bot.pk, message.chat.id), encoding="utf-8")
else:
tag = ""
if tag:
tag = str(tag)
if is_super_group and bot.enable_threads: if is_super_group and bot.enable_threads:
thread_first_message = await _redis.get(_thread_uniqie_id(bot.pk, message.chat.id)) 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_unique_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)) if tag:
if message.content_type == types.ContentType.TEXT and len(message.text) + len(tag) < 4093:
new_message = await message.bot.send_message(
super_chat_id,
message.text + "\n\n" + tag,
reply_to_message_id=int(thread_first_message))
else:
new_message = await message.copy_to(super_chat_id,
reply_to_message_id=int(thread_first_message))
new_message_2 = await message.bot.send_message(
super_chat_id, reply_to_message_id=new_message.message_id, text=tag)
await _redis.set(_message_unique_id(bot.pk, new_message_2.message_id), message.chat.id,
pexpire=thread_timeout)
else:
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, tag)
await _redis.set(_thread_uniqie_id(bot.pk, message.chat.id), new_message.message_id, await _redis.set(
pexpire=ServerSettings.thread_timeout_ms()) _thread_unique_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, tag)
await _redis.set(_thread_uniqie_id(bot.pk, message.chat.id), new_message.message_id, await _redis.set(_thread_unique_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, tag)
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):
@@ -133,9 +184,8 @@ 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: if bot.enable_mailing:
_, __ = await MailingUser.get_or_create(telegram_id=message.chat.id, bot=bot) 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)
@@ -155,19 +205,23 @@ async def handle_user_message(message: types.Message, super_chat_id: int, bot):
await send_to_superchat(is_super_group, message, super_chat_id, bot) await send_to_superchat(is_super_group, message, super_chat_id, bot)
except (exceptions.Unauthorized, exceptions.ChatNotFound): except (exceptions.Unauthorized, exceptions.ChatNotFound):
return SendMessage(chat_id=message.chat.id, text=_("Не удаётся связаться с владельцем бота")) return SendMessage(chat_id=message.chat.id, text=_("Не удаётся связаться с владельцем бота"))
except exceptions.RetryAfter:
return SendMessage(chat_id=message.chat.id, text=_("Слишком много сообщений, подождите одну минуту"),
reply_to_message_id=message.message_id)
except exceptions.TelegramAPIError as err: except exceptions.TelegramAPIError as err:
_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:
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):
@@ -202,6 +256,15 @@ async def handle_operator_message(message: types.Message, super_chat_id: int, bo
else: else:
await banned_user.delete() await banned_user.delete()
return SendMessage(chat_id=message.chat.id, text=_("Пользователь разбанен")) return SendMessage(chat_id=message.chat.id, text=_("Пользователь разбанен"))
if bot.enable_tags:
if message.text and message.text.startswith("/tag "):
tag = message.text.replace("/tag ", "")[:20].strip()
if tag:
await _redis.set(_tag_uid(bot.pk, chat_id), tag, pexpire=ServerSettings.redis_timeout_ms())
return SendMessage(chat_id=message.chat.id, text=_("Тег выставлен"))
else:
await _redis.delete(_tag_uid(bot.pk, chat_id))
return SendMessage(chat_id=message.chat.id, text=_("Тег убран"))
try: try:
await message.copy_to(chat_id) await message.copy_to(chat_id)
@@ -215,6 +278,8 @@ async def handle_operator_message(message: types.Message, super_chat_id: int, bo
elif super_chat_id > 0: elif super_chat_id > 0:
# в супер-чате кто-то пишет сообщение сам себе, только для личных сообщений # в супер-чате кто-то пишет сообщение сам себе, только для личных сообщений
if bot.enable_mailing:
asyncio.create_task(MailingUser.get_or_create(telegram_id=message.chat.id, bot=bot))
await message.forward(super_chat_id) await message.forward(super_chat_id)
# И отправить пользователю специальный текст, если он указан # И отправить пользователю специальный текст, если он указан
if bot.second_text: if bot.second_text:
@@ -227,7 +292,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")