From 120fdef189b7f3d0100819ea221bd135fc603dd2 Mon Sep 17 00:00:00 2001 From: er8dd Date: Sat, 2 Mar 2024 16:59:41 +0400 Subject: [PATCH] mailing second iteration --- Dockerfile | 2 +- locales/en/LC_MESSAGES/olgram.po | 338 +++++++++++------- locales/uk/LC_MESSAGES/olgram.po | 329 ++++++++++------- olgram/commands/bot_actions.py | 43 ++- olgram/commands/menu.py | 117 +++++- .../models/21_20240301222228_update.sql | 10 + olgram/settings.py | 3 +- olgram/utils/mix.py | 32 +- server/custom.py | 2 + 9 files changed, 611 insertions(+), 265 deletions(-) create mode 100644 olgram/migrations/models/21_20240301222228_update.sql diff --git a/Dockerfile b/Dockerfile index 6b4d454..b82ca41 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM python:3.8-buster ENV PYTHONUNBUFFERED=1 \ - POETRY_VERSION=1.1.12 \ + POETRY_VERSION=1.5.1 \ POETRY_VIRTUALENVS_CREATE="false" RUN apt-get update && \ diff --git a/locales/en/LC_MESSAGES/olgram.po b/locales/en/LC_MESSAGES/olgram.po index 4d0ca73..fd62a2a 100644 --- a/locales/en/LC_MESSAGES/olgram.po +++ b/locales/en/LC_MESSAGES/olgram.po @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" -"POT-Creation-Date: 2022-09-02 05:02+0400\n" -"PO-Revision-Date: 2022-09-02 05:07+0400\n" +"POT-Creation-Date: 2024-03-02 19:47+0400\n" +"PO-Revision-Date: 2024-03-02 19:48+0400\n" "Last-Translator: \n" "Language-Team: \n" "Language: en_US\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\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/promo.py:23 olgram/commands/promo.py:39 @@ -62,27 +62,27 @@ msgstr "Cancel" msgid "Отправлено" msgstr "Sent" -#: olgram/commands/bot_actions.py:22 +#: olgram/commands/bot_actions.py:27 msgid "Бот удалён" 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 "Текст сброшен" msgstr "Text is reset" -#: olgram/commands/bot_actions.py:64 +#: olgram/commands/bot_actions.py:81 msgid "Выбран личный чат" msgstr "Personal chat selected" -#: olgram/commands/bot_actions.py:77 +#: olgram/commands/bot_actions.py:94 msgid "Бот вышел из чатов" msgstr "Bot leaved chats" -#: olgram/commands/bot_actions.py:83 +#: olgram/commands/bot_actions.py:100 msgid "Нельзя привязать бота к этому чату" 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}" msgstr "Selected chat {0}" @@ -195,7 +195,11 @@ msgstr "All bots have outgoing messages: {0}\n" msgid "Промо-кодов выдано: {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 "" "\n" " У вас нет добавленных ботов.\n" @@ -209,25 +213,25 @@ msgstr "" " Send the command /addbot to add a bot.\n" " " -#: olgram/commands/menu.py:46 +#: olgram/commands/menu.py:48 msgid "Ваши боты" msgstr "Your bots" -#: olgram/commands/menu.py:67 +#: olgram/commands/menu.py:69 msgid "Личные сообщения" msgstr "Personal messages" -#: olgram/commands/menu.py:72 +#: olgram/commands/menu.py:74 msgid "❗️ Выйти из всех чатов" msgstr "❗️ Leave all chats" -#: olgram/commands/menu.py:77 olgram/commands/menu.py:122 -#: olgram/commands/menu.py:148 olgram/commands/menu.py:184 -#: olgram/commands/menu.py:247 +#: olgram/commands/menu.py:79 olgram/commands/menu.py:124 +#: olgram/commands/menu.py:156 olgram/commands/menu.py:209 +#: olgram/commands/menu.py:390 msgid "<< Назад" msgstr "<< Back" -#: olgram/commands/menu.py:83 +#: olgram/commands/menu.py:85 msgid "" "\n" " Этот бот не добавлен в чаты, поэтому все сообщения будут приходить " @@ -249,7 +253,7 @@ msgstr "" " again.\n" " " -#: olgram/commands/menu.py:90 +#: olgram/commands/menu.py:92 msgid "" "\n" " В этом разделе вы можете привязать бота @{0} к чату.\n" @@ -261,27 +265,31 @@ msgstr "" " Select the chat room where the bot will forward messages.\n" " " -#: olgram/commands/menu.py:102 +#: olgram/commands/menu.py:104 msgid "Текст" msgstr "Text" -#: olgram/commands/menu.py:107 +#: olgram/commands/menu.py:109 msgid "Чат" msgstr "Chat" -#: olgram/commands/menu.py:112 +#: olgram/commands/menu.py:114 msgid "Удалить бот" msgstr "Delete bot" -#: olgram/commands/menu.py:117 +#: olgram/commands/menu.py:119 msgid "Статистика" msgstr "Statistics" -#: olgram/commands/menu.py:126 +#: olgram/commands/menu.py:128 msgid "Опции" 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 "" "\n" " Управление ботом @{0}.\n" @@ -299,11 +307,11 @@ msgstr "" " @civsocit_feedback_bot\n" " " -#: olgram/commands/menu.py:143 +#: olgram/commands/menu.py:151 msgid "Да, удалить бот" msgstr "Yes, delete the bot" -#: olgram/commands/menu.py:152 +#: olgram/commands/menu.py:160 msgid "" "\n" " Вы уверены, что хотите удалить бота @{0}?\n" @@ -313,43 +321,73 @@ msgstr "" " Are you sure you want to delete the bot @{0}?\n" " " -#: olgram/commands/menu.py:161 +#: olgram/commands/menu.py:169 msgid "Потоки сообщений" msgstr "Message threads" -#: olgram/commands/menu.py:166 +#: olgram/commands/menu.py:174 msgid "Данные пользователя" msgstr "User data" -#: olgram/commands/menu.py:171 +#: olgram/commands/menu.py:179 msgid "Антифлуд" 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 подпись" msgstr "Olgram signature" -#: olgram/commands/menu.py:189 olgram/commands/menu.py:190 +#: olgram/commands/menu.py:214 olgram/commands/menu.py:215 msgid "включены" msgstr "enabled" -#: olgram/commands/menu.py:189 olgram/commands/menu.py:190 +#: olgram/commands/menu.py:214 olgram/commands/menu.py:215 msgid "выключены" msgstr "disabled" -#: olgram/commands/menu.py:191 +#: olgram/commands/menu.py:216 #, fuzzy #| msgid "включены" msgid "включен" msgstr "enabled" -#: olgram/commands/menu.py:191 +#: olgram/commands/menu.py:216 olgram/commands/menu.py:217 #, fuzzy #| msgid "выключены" msgid "выключен" 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 "" "\n" " Данные пользователя: {1}\n" " Антифлуд: {2}\n" +" Автоответчик всегда: {3}\n" +" Прерывать поток: {4}\n" +" Рассылка: {5}\n" " " msgstr "" "\n" @@ -366,60 +410,86 @@ msgstr "" " User data: {1}\n" " Antiflood: {2}" +"html#antiflood\">Anti-flood: {2}\n" +" Autorespond always: {3}\n" +" Interrupt threads: {4}\n" +" Mailing: {5}\n" +" " -#: olgram/commands/menu.py:199 -msgid "включена" -msgstr "enabled" - -#: olgram/commands/menu.py:199 -msgid "выключена" -msgstr "disabled" - -#: olgram/commands/menu.py:200 +#: olgram/commands/menu.py:232 msgid "Olgram подпись: {0}" msgstr "Olgram signature: {0}" -#: olgram/commands/menu.py:210 olgram/commands/menu.py:272 -#: olgram/commands/menu.py:314 +#: olgram/commands/menu.py:259 olgram/commands/menu.py:421 +#: olgram/commands/menu.py:480 msgid "<< Завершить редактирование" msgstr "<< Finish editing" -#: olgram/commands/menu.py:214 +#: olgram/commands/menu.py:263 msgid "Автоответчик" msgstr "Autoresponder" -#: olgram/commands/menu.py:219 olgram/commands/menu.py:286 +#: olgram/commands/menu.py:268 olgram/commands/menu.py:435 msgid "Сбросить текст" 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 "" "\n" " Сейчас вы редактируете текст, который отправляется после того, как " "пользователь отправит вашему боту @{0}\n" " команду /start\n" "\n" -" Текущий текст:\n" -"
\n"
-"    {1}\n"
-"    
\n" +" Текущий текст{2}:\n" +"
{1}
\n" " Отправьте сообщение, чтобы изменить текст.\n" " " 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" -" You are now editing the text that is sent after the user sends your bot " -"@{0}\n" -" /start command.\n" +" Напишите сообщение, которое нужно разослать всем подписчикам вашего бота " +"@{0}. \n" +" У сообщения будет до {1} получателей. \n" +" Учтите, что\n" +" 1. Рассылается только одно сообщение за раз (в т.ч. только одна " +"картинка)\n" +" 2. Когда рассылка запущена, её нельзя отменить \n" +" " +msgstr "" "\n" -" Current text:\n" -"
\n"
-"    {1}\n"
-"    
\n" -" Send a message to change the text.\n" +" Please send mailing message to send all @{0} subscribers. \n" +" Message will have up to {1} recipients. \n" +" Take note:\n" +" 1. Only one message per mailing\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 "" "\n" " Статистика по боту @{0}\n" @@ -439,45 +509,32 @@ msgstr "" " Banned users: {4}\n" " " -#: olgram/commands/menu.py:276 +#: olgram/commands/menu.py:425 msgid "Предыдущий текст" msgstr "Previous text" -#: olgram/commands/menu.py:281 +#: olgram/commands/menu.py:430 msgid "Шаблоны ответов..." msgstr "Answer templates..." -#: olgram/commands/menu.py:291 +#: olgram/commands/menu.py:457 msgid "" "\n" " Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в " "ответ на все входящие сообщения @{0} автоматически. По умолчанию оно " "отключено.\n" "\n" -" Текущий текст:\n" -"
"
-"    {1}\n"
-"    
\n" +" Текущий текст{2}:\n" +"
{1}
\n" " Отправьте сообщение, чтобы изменить текст.\n" " " 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" -"
\n"
-"    {1}\n"
-"    
\n" -" Send a message to change the text.\n" -" " -#: olgram/commands/menu.py:301 -msgid "(отключено)" -msgstr "(disabled)" +#: olgram/commands/menu.py:466 +msgid "отключено" +msgstr "disabled" -#: olgram/commands/menu.py:318 +#: olgram/commands/menu.py:484 msgid "" "\n" " Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n" @@ -503,30 +560,50 @@ msgstr "" " To remove a template from the list, send its number in the list (for " "example, 4) " -#: olgram/commands/menu.py:337 +#: olgram/commands/menu.py:503 msgid "(нет шаблонов)" msgstr "(no templates)" -#: olgram/commands/menu.py:376 +#: olgram/commands/menu.py:565 msgid "У вас нет шаблонов, чтобы их удалять" msgstr "You don't have templates to delete them" -#: olgram/commands/menu.py:378 +#: olgram/commands/menu.py:567 msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}" msgstr "To delete a template, enter a number between 0 and {0}" -#: olgram/commands/menu.py:386 +#: olgram/commands/menu.py:575 msgid "У вашего бота уже слишком много шаблонов" msgstr "Your bot already has too many templates" -#: olgram/commands/menu.py:390 +#: olgram/commands/menu.py:579 msgid "Такой текст уже есть в списке шаблонов" msgstr "This text is already in the list of templates" -#: olgram/commands/menu.py:408 +#: olgram/commands/menu.py:597 msgid "У вас нет прав на этого бота" 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 msgid "" "Новый промокод\n" @@ -551,8 +628,8 @@ msgstr "Promotion code withdrawn" msgid "" "Укажите аргумент: промокод. Например:
/setpromo my-promo-code
" msgstr "" -"Specify the argument: promo code. For example:
/setpromo my-promo-"
-"code
" +"Specify the argument: promo code. For example:
/setpromo my-promo-code"
 
 #: olgram/commands/promo.py:78 olgram/commands/promo.py:82
 msgid "Промокод не найден"
@@ -619,87 +696,102 @@ msgstr ""
 "    Write your question and we will answer you shortly.\n"
 "    "
 
-#: olgram/utils/permissions.py:40
+#: olgram/utils/permissions.py:41
 msgid "Владелец бота ограничил доступ к этому функционалу 😞"
 msgstr "The bot owner has restricted access to this functionality 😞"
 
-#: olgram/utils/permissions.py:52
+#: olgram/utils/permissions.py:53
 msgid "Владелец бота ограничил доступ к этому функционалу😞"
 msgstr "The owner of the bot has restricted access to this function😞"
 
-#: server/custom.py:55
+#: server/custom.py:57
 msgid ""
 "Политика конфиденциальности\n"
 "\n"
 "Этот бот не хранит ваши сообщения, имя пользователя и @username. При "
 "отправке сообщения (кроме команд /start и /security_policy) ваш "
 "идентификатор пользователя записывается в кеш на некоторое время и потом "
-"удаляется из кеша. Этот идентификатор используется только для общения с "
-"оператором; боты Olgram не делают массовых рассылок.\n"
+"удаляется из кеша. Этот идентификатор используется для общения с "
+"оператором.\n"
 "\n"
 msgstr ""
 "Privacy Policy.\n"
 "\n"
 "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 "
-"for a while and then deleted from the cache. This ID is only used for "
-"communicating with the operator; Olgram bots do not do mass mailings.\n"
+"for a while and then deleted from the cache. This ID is used for "
+"communicating with the operator\n"
 "\n"
 
-#: server/custom.py:61
+#: server/custom.py:62
 msgid ""
 "При отправке сообщения (кроме команд /start и /security_policy) оператор "
 "видит ваши имя пользователя, @username и идентификатор пользователя в "
-"силу настроек, которые оператор указал при создании бота."
+"силу настроек, которые оператор указал при создании бота.\n"
+"\n"
 msgstr ""
 "When sending a message (except /start and /security_policy), the operator "
 "sees 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 ""
 "В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
-"видеть ваш username, имя пользователя и другую информацию."
+"видеть ваш username, имя пользователя и другую информацию.\n"
+"\n"
 msgstr ""
 "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 "Сообщение от пользователя "
 msgstr "Message from the user "
 
-#: server/custom.py:135
+#: server/custom.py:157
 msgid "Вы заблокированы в этом боте"
 msgstr "You are blocked in this bot"
 
-#: server/custom.py:141
+#: server/custom.py:163
 msgid "Слишком много сообщений, подождите одну минуту"
 msgstr "Too many messages, wait one minute"
 
-#: server/custom.py:148
+#: server/custom.py:170
 msgid "Не удаётся связаться с владельцем бота"
 msgstr "Cannot contact the owner of the bot"
 
-#: server/custom.py:179
+#: server/custom.py:202
 msgid ""
 "Невозможно переслать сообщение: автор не найден (сообщение слишком "
 "старое?)"
 msgstr ""
 "Cannot forward this message: author not found (message too old?)"
 
-#: server/custom.py:187
+#: server/custom.py:210
 msgid "Пользователь заблокирован"
 msgstr "User is blocked"
 
-#: server/custom.py:192
+#: server/custom.py:215
 msgid "Пользователь не был забанен"
 msgstr "The user was not banned"
 
-#: server/custom.py:195
+#: server/custom.py:218
 msgid "Пользователь разбанен"
 msgstr "A user has been unlocked"
 
-#: server/custom.py:200
+#: server/custom.py:223
 msgid "Невозможно переслать сообщение (автор заблокировал бота?)"
 msgstr "Cannot forward the message (has the author blocked the bot?)"
 
@@ -711,11 +803,11 @@ msgstr "(Re)launch the bot"
 msgid "Политика конфиденциальности"
 msgstr "Privacy Policy"
 
-msgid ""
-"\n"
-"\n"
-"Этот бот создан с помощью @OlgramBot"
-msgstr ""
-"\n"
-"\n"
-"This bot was created using @OlgramBot"
+#~ msgid ""
+#~ "\n"
+#~ "\n"
+#~ "Этот бот создан с помощью @OlgramBot"
+#~ msgstr ""
+#~ "\n"
+#~ "\n"
+#~ "This bot was created using @OlgramBot"
diff --git a/locales/uk/LC_MESSAGES/olgram.po b/locales/uk/LC_MESSAGES/olgram.po
index 303676e..d610f0b 100644
--- a/locales/uk/LC_MESSAGES/olgram.po
+++ b/locales/uk/LC_MESSAGES/olgram.po
@@ -5,8 +5,8 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: \n"
-"POT-Creation-Date: 2022-09-02 05:07+0400\n"
-"PO-Revision-Date: 2022-09-02 05:12+0400\n"
+"POT-Creation-Date: 2024-03-02 19:47+0400\n"
+"PO-Revision-Date: 2024-03-02 19:48+0400\n"
 "Last-Translator: \n"
 "Language-Team: \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 && "
 "n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\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/promo.py:23 olgram/commands/promo.py:39
@@ -63,27 +63,27 @@ msgstr "Скасувати"
 msgid "Отправлено"
 msgstr "Надіслано"
 
-#: olgram/commands/bot_actions.py:22
+#: olgram/commands/bot_actions.py:27
 msgid "Бот удалён"
 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 "Текст сброшен"
 msgstr "Текст скинутий"
 
-#: olgram/commands/bot_actions.py:64
+#: olgram/commands/bot_actions.py:81
 msgid "Выбран личный чат"
 msgstr "Вибраний особистий чат"
 
-#: olgram/commands/bot_actions.py:77
+#: olgram/commands/bot_actions.py:94
 msgid "Бот вышел из чатов"
 msgstr "Бот вийшов із чатів"
 
-#: olgram/commands/bot_actions.py:83
+#: olgram/commands/bot_actions.py:100
 msgid "Нельзя привязать бота к этому чату"
 msgstr "Не можна прив'язати робота до цього чату"
 
-#: olgram/commands/bot_actions.py:87
+#: olgram/commands/bot_actions.py:104
 msgid "Выбран чат {0}"
 msgstr "Вибраний чат {0}"
 
@@ -198,7 +198,11 @@ msgstr "Вихідних повідомлень у всіх роботів: {0}\
 msgid "Промо-кодов выдано: {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 ""
 "\n"
 "        У вас нет добавленных ботов.\n"
@@ -213,25 +217,25 @@ msgstr ""
 "        \n"
 "        "
 
-#: olgram/commands/menu.py:46
+#: olgram/commands/menu.py:48
 msgid "Ваши боты"
 msgstr "Ваші боти"
 
-#: olgram/commands/menu.py:67
+#: olgram/commands/menu.py:69
 msgid "Личные сообщения"
 msgstr "Особисті повідомлення"
 
-#: olgram/commands/menu.py:72
+#: olgram/commands/menu.py:74
 msgid "❗️ Выйти из всех чатов"
 msgstr "❗️ Вийти зі всіх чатів"
 
-#: olgram/commands/menu.py:77 olgram/commands/menu.py:122
-#: olgram/commands/menu.py:148 olgram/commands/menu.py:184
-#: olgram/commands/menu.py:247
+#: olgram/commands/menu.py:79 olgram/commands/menu.py:124
+#: olgram/commands/menu.py:156 olgram/commands/menu.py:209
+#: olgram/commands/menu.py:390
 msgid "<< Назад"
 msgstr "<< Назад"
 
-#: olgram/commands/menu.py:83
+#: olgram/commands/menu.py:85
 msgid ""
 "\n"
 "        Этот бот не добавлен в чаты, поэтому все сообщения будут приходить "
@@ -254,7 +258,7 @@ msgstr ""
 "        \n"
 "        "
 
-#: olgram/commands/menu.py:90
+#: olgram/commands/menu.py:92
 msgid ""
 "\n"
 "        В этом разделе вы можете привязать бота @{0} к чату.\n"
@@ -267,27 +271,31 @@ msgstr ""
 "        \n"
 "        "
 
-#: olgram/commands/menu.py:102
+#: olgram/commands/menu.py:104
 msgid "Текст"
 msgstr "Текст"
 
-#: olgram/commands/menu.py:107
+#: olgram/commands/menu.py:109
 msgid "Чат"
 msgstr "Чат"
 
-#: olgram/commands/menu.py:112
+#: olgram/commands/menu.py:114
 msgid "Удалить бот"
 msgstr "Видалити бот"
 
-#: olgram/commands/menu.py:117
+#: olgram/commands/menu.py:119
 msgid "Статистика"
 msgstr "Статистика"
 
-#: olgram/commands/menu.py:126
+#: olgram/commands/menu.py:128
 msgid "Опции"
 msgstr "Опції"
 
-#: olgram/commands/menu.py:131
+#: olgram/commands/menu.py:134 olgram/commands/menu.py:190
+msgid "Рассылка"
+msgstr ""
+
+#: olgram/commands/menu.py:139
 msgid ""
 "\n"
 "    Управление ботом @{0}.\n"
@@ -305,11 +313,11 @@ msgstr ""
 "    @civsocit_feedback_bot\n"
 "    "
 
-#: olgram/commands/menu.py:143
+#: olgram/commands/menu.py:151
 msgid "Да, удалить бот"
 msgstr "Так, видалити бот"
 
-#: olgram/commands/menu.py:152
+#: olgram/commands/menu.py:160
 msgid ""
 "\n"
 "    Вы уверены, что хотите удалить бота @{0}?\n"
@@ -319,43 +327,75 @@ msgstr ""
 "    Ви впевнені, що хочете видалити бота @{0}?\n"
 "    "
 
-#: olgram/commands/menu.py:161
+#: olgram/commands/menu.py:169
 msgid "Потоки сообщений"
 msgstr "Потоки повідомлень"
 
-#: olgram/commands/menu.py:166
+#: olgram/commands/menu.py:174
 msgid "Данные пользователя"
 msgstr "Дані користувача"
 
-#: olgram/commands/menu.py:171
+#: olgram/commands/menu.py:179
 msgid "Антифлуд"
 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 подпись"
 msgstr "Olgram підпис"
 
-#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
+#: olgram/commands/menu.py:214 olgram/commands/menu.py:215
 msgid "включены"
 msgstr "включені"
 
-#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
+#: olgram/commands/menu.py:214 olgram/commands/menu.py:215
 msgid "выключены"
 msgstr "вимкнені"
 
-#: olgram/commands/menu.py:191
+#: olgram/commands/menu.py:216
 #, fuzzy
 #| msgid "включены"
 msgid "включен"
 msgstr "включені"
 
-#: olgram/commands/menu.py:191
+#: olgram/commands/menu.py:216 olgram/commands/menu.py:217
 #, fuzzy
 #| msgid "выключены"
 msgid "выключен"
 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 ""
 "\n"
 "    Данные пользователя: {1}\n"
 "    Антифлуд: {2}\n"
+"    Автоответчик всегда: {3}\n"
+"    Прерывать поток: {4}\n"
+"    Рассылка: {5}\n"
 "    "
 msgstr ""
-"\n"
-"    Потоки повідомлень: {0}\n"
-"    Дані користувача: {1}\n"
-"    Anti-flood: {2}"
 
-#: olgram/commands/menu.py:199
-msgid "включена"
-msgstr "включена"
-
-#: olgram/commands/menu.py:199
-msgid "выключена"
-msgstr "вимкнена"
-
-#: olgram/commands/menu.py:200
+#: olgram/commands/menu.py:232
 msgid "Olgram подпись: {0}"
 msgstr "Olgram підпис: {0}"
 
-#: olgram/commands/menu.py:210 olgram/commands/menu.py:272
-#: olgram/commands/menu.py:314
+#: olgram/commands/menu.py:259 olgram/commands/menu.py:421
+#: olgram/commands/menu.py:480
 msgid "<< Завершить редактирование"
 msgstr "<< Завершити редагування"
 
-#: olgram/commands/menu.py:214
+#: olgram/commands/menu.py:263
 msgid "Автоответчик"
 msgstr "Автовідповідач"
 
-#: olgram/commands/menu.py:219 olgram/commands/menu.py:286
+#: olgram/commands/menu.py:268 olgram/commands/menu.py:435
 msgid "Сбросить текст"
 msgstr "Скинути текст"
 
-#: olgram/commands/menu.py:224
+#: olgram/commands/menu.py:273 olgram/commands/menu.py:440
+msgid "[все языки]"
+msgstr ""
+
+#: olgram/commands/menu.py:290
 msgid ""
 "\n"
 "    Сейчас вы редактируете текст, который отправляется после того, как "
 "пользователь отправит вашему боту @{0}\n"
 "    команду /start\n"
 "\n"
-"    Текущий текст:\n"
+"    Текущий текст{2}:\n"
 "    
{1}
\n" " Отправьте сообщение, чтобы изменить текст.\n" " " msgstr "" -"\n" -" Зараз ви редагуєте текст, який надсилається після того, як користувач " -"надішле вашому боту @{0}\n" -" команду /start\n" -"\n" -" Поточний текст:\n" -"
{1}
\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 "" "\n" " Статистика по боту @{0}\n" @@ -442,43 +498,32 @@ msgstr "" " Забанено користувачів: {4}\n" " " -#: olgram/commands/menu.py:276 +#: olgram/commands/menu.py:425 msgid "Предыдущий текст" msgstr "Попередній текст" -#: olgram/commands/menu.py:281 +#: olgram/commands/menu.py:430 msgid "Шаблоны ответов..." msgstr "Шаблони відповідей..." -#: olgram/commands/menu.py:291 +#: olgram/commands/menu.py:457 msgid "" "\n" " Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в " "ответ на все входящие сообщения @{0} автоматически. По умолчанию оно " "отключено.\n" "\n" -" Текущий текст:\n" +" Текущий текст{2}:\n" "
{1}
\n" " Отправьте сообщение, чтобы изменить текст.\n" " " msgstr "" -"\n" -" Зараз ви редагуєте текст автовідповідача. Це повідомлення надсилається у " -"відповідь на всі вхідні повідомлення @{0} автоматично. За замовчуванням його " -"вимкнено.\n" -"\n" -" Поточний текст:\n" -"
\n"
-"    {1}\n"
-"    
\n" -" Надішліть повідомлення, щоб змінити текст.\n" -" " -#: olgram/commands/menu.py:301 -msgid "(отключено)" -msgstr "(відключено)" +#: olgram/commands/menu.py:466 +msgid "отключено" +msgstr "відключено" -#: olgram/commands/menu.py:318 +#: olgram/commands/menu.py:484 msgid "" "\n" " Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n" @@ -504,30 +549,50 @@ msgstr "" " \n" " " -#: olgram/commands/menu.py:337 +#: olgram/commands/menu.py:503 msgid "(нет шаблонов)" msgstr "(Немає шаблонів)" -#: olgram/commands/menu.py:376 +#: olgram/commands/menu.py:565 msgid "У вас нет шаблонов, чтобы их удалять" msgstr "У вас немає шаблонів, щоб їх видаляти" -#: olgram/commands/menu.py:378 +#: olgram/commands/menu.py:567 msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}" msgstr "Неправильне число. Щоб видалити шаблон, введіть число від 0 до {0}" -#: olgram/commands/menu.py:386 +#: olgram/commands/menu.py:575 msgid "У вашего бота уже слишком много шаблонов" msgstr "У вашого бота вже дуже багато шаблонів" -#: olgram/commands/menu.py:390 +#: olgram/commands/menu.py:579 msgid "Такой текст уже есть в списке шаблонов" msgstr "Такий текст вже є у списку шаблонів" -#: olgram/commands/menu.py:408 +#: olgram/commands/menu.py:597 msgid "У вас нет прав на этого бота" 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 msgid "" "Новый промокод\n" @@ -623,23 +688,23 @@ msgstr "" " \n" " " -#: olgram/utils/permissions.py:40 +#: olgram/utils/permissions.py:41 msgid "Владелец бота ограничил доступ к этому функционалу 😞" msgstr "Власник бота обмежив доступ до цього функціоналу 😞" -#: olgram/utils/permissions.py:52 +#: olgram/utils/permissions.py:53 msgid "Владелец бота ограничил доступ к этому функционалу😞" msgstr "Власник бота обмежив доступ до цього функціоналу 😞" -#: server/custom.py:55 +#: server/custom.py:57 msgid "" "Политика конфиденциальности\n" "\n" "Этот бот не хранит ваши сообщения, имя пользователя и @username. При " "отправке сообщения (кроме команд /start и /security_policy) ваш " "идентификатор пользователя записывается в кеш на некоторое время и потом " -"удаляется из кеша. Этот идентификатор используется только для общения с " -"оператором; боты Olgram не делают массовых рассылок.\n" +"удаляется из кеша. Этот идентификатор используется для общения с " +"оператором.\n" "\n" msgstr "" "Політика конфіденційності\n" @@ -647,45 +712,63 @@ msgstr "" "Цей бот не зберігає ваші повідомлення, ім'я користувача та @username. При " "надсиланні повідомлення (крім команд /start та /security_policy) ваш " "ідентифікатор користувача записується в кеш на деякий час і потім " -"видаляється з кеша. Цей ідентифікатор використовується лише для спілкування " -"з оператором; боти Olgram не роблять масових розсилок.\n" +"видаляється з кеша. Цей ідентифікатор використовується для спілкування з " +"оператором.\n" "\n" -#: server/custom.py:61 +#: server/custom.py:62 msgid "" "При отправке сообщения (кроме команд /start и /security_policy) оператор " "видит ваши имя пользователя, @username и идентификатор пользователя в " -"силу настроек, которые оператор указал при создании бота." +"силу настроек, которые оператор указал при создании бота.\n" +"\n" msgstr "" "При надсиланні повідомлення (крім команд /start та /security_policy) " "оператор бачить ваше ім'я користувача, @username та ідентифікатор " -"користувача через налаштування, які оператор вказав при створенні бота." +"користувача через налаштування, які оператор вказав при створенні бота.\n" +"\n" -#: server/custom.py:65 +#: server/custom.py:66 msgid "" "В зависимости от ваших настроек конфиденциальности Telegram, оператор может " -"видеть ваш username, имя пользователя и другую информацию." +"видеть ваш username, имя пользователя и другую информацию.\n" +"\n" msgstr "" "Залежно від ваших налаштувань конфіденційності 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 "Сообщение от пользователя " msgstr "Допис від користувача " -#: server/custom.py:135 +#: server/custom.py:157 msgid "Вы заблокированы в этом боте" msgstr "Ви заблоковані у цьому боті" -#: server/custom.py:141 +#: server/custom.py:163 msgid "Слишком много сообщений, подождите одну минуту" msgstr "Забагато повідомлень, зачекайте одну хвилину" -#: server/custom.py:148 +#: server/custom.py:170 msgid "Не удаётся связаться с владельцем бота" msgstr "Не вдається зв'язатися з власником бота" -#: server/custom.py:179 +#: server/custom.py:202 msgid "" "Невозможно переслать сообщение: автор не найден (сообщение слишком " "старое?)" @@ -693,19 +776,19 @@ msgstr "" "Неможливо надіслати повідомлення: автора не знайдено (повідомлення " "занадто старе?)" -#: server/custom.py:187 +#: server/custom.py:210 msgid "Пользователь заблокирован" msgstr "Користувач заблоковано" -#: server/custom.py:192 +#: server/custom.py:215 msgid "Пользователь не был забанен" msgstr "Користувач не був забанений" -#: server/custom.py:195 +#: server/custom.py:218 msgid "Пользователь разбанен" msgstr "Користувач розбанений" -#: server/custom.py:200 +#: server/custom.py:223 msgid "Невозможно переслать сообщение (автор заблокировал бота?)" msgstr "Неможливо надіслати повідомлення (автор заблокував робота?)" @@ -717,11 +800,11 @@ msgstr "(Пере) запустити бота" msgid "Политика конфиденциальности" msgstr "Політика конфіденційності" -msgid "" -"\n" -"\n" -"Этот бот создан с помощью @OlgramBot" -msgstr "" -"\n" -"\n" -"Цей бот створено за допомогою @OlgramBot" +#~ msgid "" +#~ "\n" +#~ "\n" +#~ "Этот бот создан с помощью @OlgramBot" +#~ msgstr "" +#~ "\n" +#~ "\n" +#~ "Цей бот створено за допомогою @OlgramBot" diff --git a/olgram/commands/bot_actions.py b/olgram/commands/bot_actions.py index a4e043e..1eeee75 100644 --- a/olgram/commands/bot_actions.py +++ b/olgram/commands/bot_actions.py @@ -1,8 +1,13 @@ """ Здесь работа с конкретным ботом """ +import logging + from aiogram import types -from aiogram.utils.exceptions import TelegramAPIError, Unauthorized +from asyncio import sleep +from datetime import datetime +from olgram.utils.mix import send_stored_message +from aiogram.utils import exceptions from aiogram import Bot as AioBot from olgram.models.models import Bot, BotStartMessage, BotSecondMessage from server.server import unregister_token @@ -15,14 +20,14 @@ async def delete_bot(bot: Bot, call: types.CallbackQuery): """ try: await unregister_token(bot.decrypted_token()) - except Unauthorized: + except exceptions.Unauthorized: # Вероятно пользователь сбросил токен или удалил бот, это уже не наши проблемы pass await bot.delete() await call.answer(_("Бот удалён")) try: await call.message.delete() - except TelegramAPIError: + except exceptions.TelegramAPIError: pass @@ -84,7 +89,7 @@ async def select_chat(bot: Bot, call: types.CallbackQuery, chat: str): try: await chat.delete() await a_bot.leave_chat(chat.chat_id) - except TelegramAPIError: + except exceptions.TelegramAPIError: pass await call.answer(_("Бот вышел из чатов")) await a_bot.session.close() @@ -133,3 +138,33 @@ async def antiflood(bot: Bot, call: types.CallbackQuery): async def mailing(bot: Bot, call: types.CallbackQuery): bot.enable_mailing = not bot.enable_mailing await bot.save(update_fields=["enable_mailing"]) + + +async def go_mailing(bot: Bot, context: dict) -> int: + users = await bot.mailing_users + a_bot = AioBot(bot.decrypted_token()) + + count = 0 + + for user in users: + bot.last_mailing_at = datetime.now() + await bot.save(update_fields=["last_mailing_at"]) + try: + await sleep(0.05) + try: + file_id = await send_stored_message(context, a_bot, user.telegram_id) + except exceptions.RetryAfter as err: + await sleep(err.timeout) + file_id = await send_stored_message(context, a_bot, user.telegram_id) + + if file_id: + context["mailing_id"] = file_id + count += 1 + except (exceptions.ChatNotFound, exceptions.BotBlocked, exceptions.UserDeactivated): + await user.delete() + except Exception as err: + logging.error("mailing error") + logging.error(err, exc_info=True) + pass + + return count diff --git a/olgram/commands/menu.py b/olgram/commands/menu.py index 9e8e087..fcd75df 100644 --- a/olgram/commands/menu.py +++ b/olgram/commands/menu.py @@ -1,11 +1,13 @@ +import logging +from io import BytesIO from olgram.router import dp - +from datetime import datetime, timedelta, timezone from aiogram import types, Bot as AioBot from olgram.models.models import Bot, User, DefaultAnswer, BotStartMessage, BotSecondMessage from aiogram.dispatcher import FSMContext from aiogram.utils.callback_data import CallbackData from textwrap import dedent -from olgram.utils.mix import edit_or_create, button_text_limit, wrap +from olgram.utils.mix import edit_or_create, button_text_limit, wrap, send_stored_message from olgram.commands import bot_actions from locales.locale import _ @@ -127,6 +129,12 @@ async def send_bot_menu(bot: Bot, call: types.CallbackQuery): callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="settings", chat=empty)) ) + if bot.enable_mailing: + keyboard.insert( + types.InlineKeyboardButton(text=_("Рассылка"), + callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="go_mailing", + chat=empty)) + ) await edit_or_create(call, dedent(_(""" Управление ботом @{0}. @@ -296,6 +304,83 @@ async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = await AioBot.get_current().send_message(chat_id, text, reply_markup=keyboard, parse_mode="HTML") +async def send_bot_mailing_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None, + chat_id: ty.Optional[int] = None): + if call: + await call.answer() + keyboard = types.InlineKeyboardMarkup(row_width=1) + keyboard.insert( + types.InlineKeyboardButton(text=_("<< Отменить рассылку"), + callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty)) + ) + + text = dedent(_(""" + Напишите сообщение, которое нужно разослать всем подписчикам вашего бота @{0}. + У сообщения будет до {1} получателей. + Учтите, что + 1. Рассылается только одно сообщение за раз (в т.ч. только одна картинка) + 2. Когда рассылка запущена, её нельзя отменить + """)) + text = text.format(bot.name, len(await bot.mailing_users)) + if call: + await edit_or_create(call, text, keyboard, parse_mode="HTML") + else: + await AioBot.get_current().send_message(chat_id, text, reply_markup=keyboard, parse_mode="HTML") + + +@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 + + 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") + _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, + text="Вы уверены, что хотите разослать это сообщение всем пользователям?", + reply_markup=keyboard) + + async def send_bot_statistic_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None, chat_id: ty.Optional[int] = None): if call: @@ -527,6 +612,15 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon return await send_bot_statistic_menu(bot, call) if operation == "settings": return await send_bot_settings_menu(bot, call) + if operation == "go_mailing": + 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 state.set_state("wait_mailing_text") + async with state.proxy() as proxy: + proxy["bot_id"] = bot.id + return await send_bot_mailing_menu(bot, call) if operation == "text": await state.set_state("wait_start_text") async with state.proxy() as proxy: @@ -536,6 +630,25 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon if level == "3": if operation == "delete_yes": 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": return await bot_actions.select_chat(bot, call, callback_data.get("chat")) if operation == "threads": diff --git a/olgram/migrations/models/21_20240301222228_update.sql b/olgram/migrations/models/21_20240301222228_update.sql new file mode 100644 index 0000000..c2fdc47 --- /dev/null +++ b/olgram/migrations/models/21_20240301222228_update.sql @@ -0,0 +1,10 @@ +-- upgrade -- +CREATE TABLE IF NOT EXISTS "mailinguser" ( + "id" BIGSERIAL NOT NULL PRIMARY KEY, + "telegram_id" BIGINT NOT NULL, + "bot_id" INT NOT NULL REFERENCES "bot" ("id") ON DELETE CASCADE, + CONSTRAINT "uid_mailinguser_bot_id_906a76" UNIQUE ("bot_id", "telegram_id") +); +CREATE INDEX IF NOT EXISTS "idx_mailinguser_telegra_55de60" ON "mailinguser" ("telegram_id"); +-- downgrade -- +DROP TABLE IF EXISTS "mailinguser"; diff --git a/olgram/settings.py b/olgram/settings.py index 653a1f5..eae63bc 100644 --- a/olgram/settings.py +++ b/olgram/settings.py @@ -7,7 +7,6 @@ from datetime import timedelta import typing as ty from olgram.utils.crypto import Cryptor - load_dotenv() @@ -161,4 +160,6 @@ TORTOISE_ORM = { "default_connection": "default", }, }, + "use_tz": False, + "timezone": "UTC" } diff --git a/olgram/utils/mix.py b/olgram/utils/mix.py index 2a39adb..8800c08 100644 --- a/olgram/utils/mix.py +++ b/olgram/utils/mix.py @@ -1,3 +1,5 @@ +import logging +from io import BytesIO from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup from aiogram.utils.exceptions import TelegramAPIError from aiogram import types, Bot as AioBot @@ -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") if content_type == types.ContentType.LOCATION: return await bot.send_location(chat_id, storage["mailing_location"][0], storage["mailing_location"][1]) - if content_type == types.ContentType.AUDIO: - return await bot.send_audio(chat_id, audio=storage["mailing_audio"], caption=storage.get("mailing_caption")) - if content_type == types.ContentType.DOCUMENT: - return await bot.send_document(chat_id, document=storage["mailing_document"], - caption=storage.get("mailing_caption")) - if content_type == types.ContentType.PHOTO: - return await bot.send_photo(chat_id, photo=storage["mailing_photo"], - caption=storage.get("mailing_caption")) - if content_type == types.ContentType.VIDEO: - return await bot.send_video(chat_id, video=storage["mailing_video"], - caption=storage.get("mailing_caption")) + if content_type in (types.ContentType.AUDIO, types.ContentType.VIDEO, types.ContentType.DOCUMENT, + types.ContentType.PHOTO): + caption = storage.get("mailing_caption") + if storage.get("mailing_id"): + logging.info("Mailing use file id") + obj = storage["mailing_id"] + else: + logging.info("Mailing upload file") + obj = types.InputFile(BytesIO(storage["mailing_data"]), filename=storage.get("mailing_file_name")) + + if content_type == types.ContentType.AUDIO: + 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") diff --git a/server/custom.py b/server/custom.py index 06df638..c5a2f69 100644 --- a/server/custom.py +++ b/server/custom.py @@ -229,6 +229,8 @@ async def handle_operator_message(message: types.Message, super_chat_id: int, bo 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) # И отправить пользователю специальный текст, если он указан if bot.second_text: