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

15 Commits

Author SHA1 Message Date
walker
58f551c77d mailing, more mailing 2022-11-07 02:03:00 +04:00
walker
696bc5368b mailing second iteration 2022-11-06 06:09:15 +04:00
walker
340246b937 mailing first iteration 2022-11-06 04:32:32 +04:00
walker
bb1456dda1 fix location forwarding 2022-11-05 00:42:11 +04:00
walker
756f0bd89a minor fixes 2022-11-05 00:40:26 +04:00
walker
6acc2068de fix for prev 2022-10-29 20:14:50 +04:00
walker
d478e9d8e9 version bump 2022-10-29 19:34:13 +04:00
walker
52864ed729 fix #19 2022-10-29 19:32:56 +04:00
mihalin
ac09e42f94 Merge pull request #24 from arcxio/multiple_admins
support multiple comma-separated values in ADMIN_ID
2022-10-29 19:20:27 +04:00
arĉi
afc5389520 support multiple comma-separated values in ADMIN_ID 2022-10-29 18:52:08 +06:00
mihalin
30ab7c84b4 update translations 2022-09-02 05:12:53 +04:00
mihalin
9d8f5a97f7 Revert "debug print"
This reverts commit 16e944707f.
2022-09-02 04:59:18 +04:00
mihalin
16e944707f debug print 2022-09-02 04:48:16 +04:00
mihalin
9723c70deb leave chat button first iteration 2022-09-02 04:28:51 +04:00
mihalin
6e2ee437ba more nice menu 2022-09-02 04:09:28 +04:00
15 changed files with 648 additions and 274 deletions

View File

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

View File

@@ -31,5 +31,5 @@ REDIS_PATH=redis://redis
# Set log level, can be CRITICAL, ERROR, WARNING, INFO, DEBUG. By default it set to WARNING.
LOGLEVEL=
# Uncomment this to switch bot language to Chinese
# O_LANG=zh
# Uncomment this to switch bot language to English
# O_LANG=en

View File

@@ -5,39 +5,88 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2022-04-11 17:53+0300\n"
"PO-Revision-Date: 2022-04-11 18:14+0300\n"
"POT-Creation-Date: 2022-09-02 05:02+0400\n"
"PO-Revision-Date: 2022-09-02 05:07+0400\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: en_US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 3.0\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: en_US\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 3.1\n"
#: olgram/commands/bot_actions.py:21
#: olgram/commands/admin.py:21 olgram/commands/info.py:21
#: olgram/commands/promo.py:23 olgram/commands/promo.py:39
msgid "Недостаточно прав"
msgstr "Not enough permissions"
#: olgram/commands/admin.py:27
msgid "Нужно указать имя бота"
msgstr "You need to specify the bot's name"
#: olgram/commands/admin.py:33
msgid "Такого бота нет в системе"
msgstr "There is no such bot"
#: olgram/commands/admin.py:39 olgram/commands/admin.py:53
msgid "Пропустить"
msgstr "Skip"
#: olgram/commands/admin.py:42
msgid ""
"Введите текст, который будет отправлен владельцу бота {0}. Напишите "
"'Пропустить' чтобы отменить"
msgstr ""
"Enter the text that will be sent to the owner of the bot {0}. Write 'Skip' "
"to cancel"
#: olgram/commands/admin.py:50
msgid "Поддерживается только текст"
msgstr "Only text is supported"
#: olgram/commands/admin.py:55 olgram/commands/admin.py:71
msgid "Отменено"
msgstr "Cancelled"
#: olgram/commands/admin.py:61 olgram/commands/admin.py:69
msgid "Отправить"
msgstr "Send"
#: olgram/commands/admin.py:62
msgid "Отменить"
msgstr "Cancel"
#: olgram/commands/admin.py:81
msgid "Отправлено"
msgstr "Sent"
#: olgram/commands/bot_actions.py:22
msgid "Бот удалён"
msgstr "Bot removed"
#: olgram/commands/bot_actions.py:37 olgram/commands/bot_actions.py:49
#: olgram/commands/bot_actions.py:38 olgram/commands/bot_actions.py:50
msgid "Текст сброшен"
msgstr ""
msgstr "Text is reset"
#: olgram/commands/bot_actions.py:63
#: olgram/commands/bot_actions.py:64
msgid "Выбран личный чат"
msgstr "Personal chat selected"
#: olgram/commands/bot_actions.py:68
#: olgram/commands/bot_actions.py:77
msgid "Бот вышел из чатов"
msgstr "Bot leaved chats"
#: olgram/commands/bot_actions.py:83
msgid "Нельзя привязать бота к этому чату"
msgstr "You can't bind a bot to this chat room"
#: olgram/commands/bot_actions.py:72
#: olgram/commands/bot_actions.py:87
msgid "Выбран чат {0}"
msgstr "Selected chat {0}"
#: olgram/commands/bots.py:42
#: olgram/commands/bots.py:46
msgid ""
"У вас уже слишком много ботов. Удалите какой-нибудь свой бот из Olgram(/"
"mybots -> (Выбрать бота) -> Удалить бот)"
@@ -45,7 +94,7 @@ msgstr ""
"You already have too many bots. Remove any of your bots from Olgram(/mybots -"
"> (Select bot) -> Remove bot)"
#: olgram/commands/bots.py:46
#: olgram/commands/bots.py:50
msgid ""
"\n"
" Чтобы подключить бот, вам нужно выполнить три действия:\n"
@@ -72,7 +121,7 @@ msgstr ""
"Chatfuel, Livegram and others).\n"
" "
#: olgram/commands/bots.py:66
#: olgram/commands/bots.py:70
msgid ""
"\n"
" Это не токен бота.\n"
@@ -88,7 +137,7 @@ msgstr ""
"abc123_AbcdEFghijKLMnopqrstu12\n"
" "
#: olgram/commands/bots.py:73
#: olgram/commands/bots.py:77
msgid ""
"\n"
" Не удалось запустить этого бота: неверный токен\n"
@@ -98,7 +147,7 @@ msgstr ""
" Failed to start this bot: Wrong token\n"
" "
#: olgram/commands/bots.py:78
#: olgram/commands/bots.py:82
msgid ""
"\n"
" Не удалось запустить этого бота: непредвиденная ошибка\n"
@@ -108,7 +157,7 @@ msgstr ""
" Failed to start this bot: unexpected error\n"
" "
#: olgram/commands/bots.py:83
#: olgram/commands/bots.py:87
msgid ""
"\n"
" Такой бот уже есть в базе данных\n"
@@ -118,35 +167,34 @@ msgstr ""
" Such a bot is already in the database\n"
" "
#: olgram/commands/bots.py:115
#: olgram/commands/bots.py:122
msgid "Бот добавлен! Список ваших ботов: /mybots"
msgstr "Bot added! List of your bots: /mybots"
#: olgram/commands/info.py:21 olgram/commands/promo.py:23
#: olgram/commands/promo.py:39
msgid "Недостаточно прав"
msgstr "Not enough permissions"
#: olgram/commands/info.py:32
#: olgram/commands/info.py:34
msgid "Количество ботов: {0}\n"
msgstr "Number of bots: {0}\n"
#: olgram/commands/info.py:33
#: olgram/commands/info.py:35
msgid "Количество пользователей (у конструктора): {0}\n"
msgstr "Number of users (at the constructor): {0}\n"
#: olgram/commands/info.py:34
#: olgram/commands/info.py:36
msgid "Шаблонов ответов: {0}\n"
msgstr "Answer templates: {0}\n"
#: olgram/commands/info.py:35
#: olgram/commands/info.py:37
msgid "Входящих сообщений у всех ботов: {0}\n"
msgstr "Incoming messages from all bots: {0}\n"
#: olgram/commands/info.py:36
#: olgram/commands/info.py:38
msgid "Исходящих сообщений у всех ботов: {0}\n"
msgstr "All bots have outgoing messages: {0}\n"
#: olgram/commands/info.py:39
msgid "Промо-кодов выдано: {0}\n"
msgstr "Promo codes issued: {0}\n"
#: olgram/commands/menu.py:31
msgid ""
"\n"
@@ -167,15 +215,19 @@ msgstr "Your bots"
#: olgram/commands/menu.py:67
msgid "Личные сообщения"
msgstr "Личные сообщения"
msgstr "Personal messages"
#: olgram/commands/menu.py:72 olgram/commands/menu.py:117
#: olgram/commands/menu.py:143 olgram/commands/menu.py:174
#: olgram/commands/menu.py:235
#: olgram/commands/menu.py:72
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
msgid "<< Назад"
msgstr "<< Back"
#: olgram/commands/menu.py:78
#: olgram/commands/menu.py:83
msgid ""
"\n"
" Этот бот не добавлен в чаты, поэтому все сообщения будут приходить "
@@ -197,7 +249,7 @@ msgstr ""
" again.\n"
" "
#: olgram/commands/menu.py:85
#: olgram/commands/menu.py:90
msgid ""
"\n"
" В этом разделе вы можете привязать бота @{0} к чату.\n"
@@ -209,27 +261,27 @@ msgstr ""
" Select the chat room where the bot will forward messages.\n"
" "
#: olgram/commands/menu.py:97
#: olgram/commands/menu.py:102
msgid "Текст"
msgstr "Text"
#: olgram/commands/menu.py:102
#: olgram/commands/menu.py:107
msgid "Чат"
msgstr "Chat"
#: olgram/commands/menu.py:107
#: olgram/commands/menu.py:112
msgid "Удалить бот"
msgstr "Delete bot"
#: olgram/commands/menu.py:112
#: olgram/commands/menu.py:117
msgid "Статистика"
msgstr "Statistics"
#: olgram/commands/menu.py:121
#: olgram/commands/menu.py:126
msgid "Опции"
msgstr "Options"
#: olgram/commands/menu.py:126
#: olgram/commands/menu.py:131
msgid ""
"\n"
" Управление ботом @{0}.\n"
@@ -247,11 +299,11 @@ msgstr ""
" @civsocit_feedback_bot\n"
" "
#: olgram/commands/menu.py:138
#: olgram/commands/menu.py:143
msgid "Да, удалить бот"
msgstr "Yes, delete the bot"
#: olgram/commands/menu.py:147
#: olgram/commands/menu.py:152
msgid ""
"\n"
" Вы уверены, что хотите удалить бота @{0}?\n"
@@ -261,68 +313,87 @@ msgstr ""
" Are you sure you want to delete the bot @{0}?\n"
" "
#: olgram/commands/menu.py:156
#: olgram/commands/menu.py:161
msgid "Потоки сообщений"
msgstr "Message threads"
#: olgram/commands/menu.py:161
#: olgram/commands/menu.py:166
msgid "Данные пользователя"
msgstr "User data"
#: olgram/commands/menu.py:168
#: olgram/commands/menu.py:171
msgid "Антифлуд"
msgstr "Antiflood"
#: olgram/commands/menu.py:178
msgid "Olgram подпись"
msgstr "Olgram signature"
#: olgram/commands/menu.py:179 olgram/commands/menu.py:180
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
msgid "включены"
msgstr "enabled"
#: olgram/commands/menu.py:179 olgram/commands/menu.py:180
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
msgid "выключены"
msgstr "disabled"
#: olgram/commands/menu.py:181
#: olgram/commands/menu.py:191
#, fuzzy
#| msgid "включены"
msgid "включен"
msgstr "enabled"
#: olgram/commands/menu.py:191
#, fuzzy
#| msgid "выключены"
msgid "выключен"
msgstr "disabled"
#: olgram/commands/menu.py:192
msgid ""
"\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#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\">Антифлуд</a>: <b>{2}</b>\n"
" "
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#threads\">Threads</a>: <b>{0}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-"
"info\">User data</a>: <b>{1}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options."
"html#antiflood\">Antiflood</a>: <b>{2}</b>"
#: olgram/commands/menu.py:187
#: olgram/commands/menu.py:199
msgid "включена"
msgstr "enabled"
#: olgram/commands/menu.py:187
#: olgram/commands/menu.py:199
msgid "выключена"
msgstr "disabled"
#: olgram/commands/menu.py:188
#: olgram/commands/menu.py:200
msgid "Olgram подпись: <b>{0}</b>"
msgstr "Olgram signature: <b>{0}</b>"
#: olgram/commands/menu.py:198 olgram/commands/menu.py:260
#: olgram/commands/menu.py:302
#: olgram/commands/menu.py:210 olgram/commands/menu.py:272
#: olgram/commands/menu.py:314
msgid "<< Завершить редактирование"
msgstr "<< Finish editing"
#: olgram/commands/menu.py:202
#: olgram/commands/menu.py:214
msgid "Автоответчик"
msgstr "Autoresponder"
#: olgram/commands/menu.py:207 olgram/commands/menu.py:274
#: olgram/commands/menu.py:219 olgram/commands/menu.py:286
msgid "Сбросить текст"
msgstr "Reset text"
#: olgram/commands/menu.py:212
#: olgram/commands/menu.py:224
msgid ""
"\n"
" Сейчас вы редактируете текст, который отправляется после того, как "
@@ -348,7 +419,7 @@ msgstr ""
" Send a message to change the text.\n"
" "
#: olgram/commands/menu.py:239
#: olgram/commands/menu.py:251
msgid ""
"\n"
" Статистика по боту @{0}\n"
@@ -368,15 +439,15 @@ msgstr ""
" Banned users: <b>{4}</b>\n"
" "
#: olgram/commands/menu.py:264
#: olgram/commands/menu.py:276
msgid "Предыдущий текст"
msgstr "Previous text"
#: olgram/commands/menu.py:269
#: olgram/commands/menu.py:281
msgid "Шаблоны ответов..."
msgstr "Answer templates..."
#: olgram/commands/menu.py:279
#: olgram/commands/menu.py:291
msgid ""
"\n"
" Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в "
@@ -402,11 +473,11 @@ msgstr ""
" Send a message to change the text.\n"
" "
#: olgram/commands/menu.py:289
#: olgram/commands/menu.py:301
msgid "(отключено)"
msgstr "(disabled)"
#: olgram/commands/menu.py:306
#: olgram/commands/menu.py:318
msgid ""
"\n"
" Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n"
@@ -432,27 +503,27 @@ msgstr ""
" To remove a template from the list, send its number in the list (for "
"example, 4) "
#: olgram/commands/menu.py:325
#: olgram/commands/menu.py:337
msgid "(нет шаблонов)"
msgstr "(no templates)"
#: olgram/commands/menu.py:364
#: olgram/commands/menu.py:376
msgid "У вас нет шаблонов, чтобы их удалять"
msgstr "You don't have templates to delete them"
#: olgram/commands/menu.py:366
#: olgram/commands/menu.py:378
msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}"
msgstr "To delete a template, enter a number between 0 and {0}"
#: olgram/commands/menu.py:374
#: olgram/commands/menu.py:386
msgid "У вашего бота уже слишком много шаблонов"
msgstr "Your bot already has too many templates"
#: olgram/commands/menu.py:378
#: olgram/commands/menu.py:390
msgid "Такой текст уже есть в списке шаблонов"
msgstr "This text is already in the list of templates"
#: olgram/commands/menu.py:396
#: olgram/commands/menu.py:408
msgid "У вас нет прав на этого бота"
msgstr "You have no permissions to this bot"
@@ -472,34 +543,35 @@ msgstr "Incorrect token"
msgid "Такого кода не существует"
msgstr "There is no such code"
#: olgram/commands/promo.py:53
#: olgram/commands/promo.py:59
msgid "Промокод отозван"
msgstr "Promotion code withdrawn"
#: olgram/commands/promo.py:64
#: olgram/commands/promo.py:70
msgid ""
"Укажите аргумент: промокод. Например: <pre>/set_promo my-promo-code</pre>"
"Укажите аргумент: промокод. Например: <pre>/setpromo my-promo-code</pre>"
msgstr ""
"Specify the argument: promo code. For example: <pre>/set_promo my-promo-"
"Specify the argument: promo code. For example: <pre>/setpromo my-promo-"
"code</pre>"
#: olgram/commands/promo.py:72 olgram/commands/promo.py:76
#: olgram/commands/promo.py:78 olgram/commands/promo.py:82
msgid "Промокод не найден"
msgstr "Promo code not found"
#: olgram/commands/promo.py:79
#: olgram/commands/promo.py:85
msgid "Промокод уже использован"
msgstr "Promo code has already been used"
#: olgram/commands/promo.py:85
#: olgram/commands/promo.py:91
msgid "Промокод активирован! Спасибо 🙌"
msgstr "Promo code activated! Thank you 🙌"
#: olgram/commands/start.py:25
#: olgram/commands/start.py:23
msgid ""
"\n"
" Olgram Bot — это конструктор ботов обратной связи в Telegram. Подробнее "
"<a href=\"https://olgram.readthedocs.io\">читайте здесь</a>.\n"
"<a href=\"https://olgram.readthedocs.io\">читайте здесь</a>. Следите за "
"обновлениями <a href=\"https://t.me/civsoc_it\">здесь</a>.\n"
"\n"
" Используйте эти команды, чтобы управлять этим ботом:\n"
"\n"
@@ -510,18 +582,18 @@ msgid ""
" "
msgstr ""
"\n"
" Olgram Bot — это конструктор ботов обратной связи в Telegram. Подробнее "
"<a href=\"https://olgram.readthedocs.io\">читайте здесь</a>.\n"
" Olgram Bot is a feedback bot contructor for Telegram. More info <a "
"href=\"https://olgram.readthedocs.io\">here</a>.\n"
"\n"
" Используйте эти команды, чтобы управлять этим ботом:\n"
" Use that commands to control bot:\n"
"\n"
" /addbot - добавить бот\n"
" /mybots - управление ботами\n"
" /addbot - add bot\n"
" /mybots - bot control\n"
"\n"
" /help - помощь\n"
" /help - help\n"
" "
#: olgram/commands/start.py:44
#: olgram/commands/start.py:43
msgid ""
"\n"
" Читайте инструкции на нашем сайте https://olgram.readthedocs.io\n"
@@ -555,7 +627,7 @@ msgstr "The bot owner has restricted access to this functionality 😞"
msgid "Владелец бота ограничил доступ к этому функционалу😞"
msgstr "The owner of the bot has restricted access to this function😞"
#: server/custom.py:47
#: server/custom.py:55
msgid ""
"<b>Политика конфиденциальности</b>\n"
"\n"
@@ -574,7 +646,7 @@ msgstr ""
"communicating with the operator; Olgram bots do not do mass mailings.\n"
"\n"
#: server/custom.py:53
#: server/custom.py:61
msgid ""
"При отправке сообщения (кроме команд /start и /security_policy) оператор "
"<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в "
@@ -584,7 +656,7 @@ msgstr ""
"<b>sees</b> your username, @username and user ID by virtue of the settings "
"that the operator specified when creating the bot."
#: server/custom.py:57
#: server/custom.py:65
msgid ""
"В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
"видеть ваш username, имя пользователя и другую информацию."
@@ -592,34 +664,42 @@ msgstr ""
"Depending on your Telegram privacy settings, the operator may see your "
"username, username and other information."
#: server/custom.py:68
#: server/custom.py:76
msgid "Сообщение от пользователя "
msgstr "Message from the user "
#: server/custom.py:96
#: server/custom.py:135
msgid "Вы заблокированы в этом боте"
msgstr "You are blocked in this bot"
#: server/custom.py:142
#: server/custom.py:141
msgid "Слишком много сообщений, подождите одну минуту"
msgstr "Too many messages, wait one minute"
#: server/custom.py:148
msgid "Не удаётся связаться с владельцем бота"
msgstr "Cannot contact the owner of the bot"
#: server/custom.py:179
msgid ""
"<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
"старое?)</i>"
msgstr ""
"<i>Cannot forward this message: author not found (message too old?)</i>"
#: server/custom.py:150
#: server/custom.py:187
msgid "Пользователь заблокирован"
msgstr "User is blocked"
#: server/custom.py:155
#: server/custom.py:192
msgid "Пользователь не был забанен"
msgstr "The user was not banned"
#: server/custom.py:158
#: server/custom.py:195
msgid "Пользователь разбанен"
msgstr "A user has been unlocked"
#: server/custom.py:163
#: server/custom.py:200
msgid "<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"
msgstr "<i>Cannot forward the message (has the author blocked the bot?)</i>"
@@ -631,21 +711,11 @@ msgstr "(Re)launch the bot"
msgid "Политика конфиденциальности"
msgstr "Privacy Policy"
msgid ""
"\n"
"\n"
"Этот бот создан с помощью @OlgramBot"
msgstr ""
"\n"
"\n"
"This bot was created using @OlgramBot"
msgid "Не удаётся связаться с владельцем бота"
msgstr "Cannot contact the owner of the bot"
msgid "Слишком много сообщений, подождите одну минуту"
msgstr "Too many messages, wait one minute"
msgid "Антифлуд"
msgstr "Antiflood"
#~ msgid ""
#~ "\n"
#~ "\n"
#~ "Этот бот создан с помощью @OlgramBot"
#~ msgstr ""
#~ "\n"
#~ "\n"
#~ "This bot was created using @OlgramBot"

View File

@@ -5,40 +5,89 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2022-04-09 06:24+0300\n"
"PO-Revision-Date: 2022-04-09 07:11+0300\n"
"POT-Creation-Date: 2022-09-02 05:07+0400\n"
"PO-Revision-Date: 2022-09-02 05:12+0400\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: uk_UA\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"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.0\n"
"Last-Translator: \n"
"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"
"Language: uk_UA\n"
"X-Generator: Poedit 3.1\n"
#: olgram/commands/bot_actions.py:21
#: olgram/commands/admin.py:21 olgram/commands/info.py:21
#: olgram/commands/promo.py:23 olgram/commands/promo.py:39
msgid "Недостаточно прав"
msgstr "Недостатньо прав"
#: olgram/commands/admin.py:27
msgid "Нужно указать имя бота"
msgstr "Потрібно вказати ім'я бота"
#: olgram/commands/admin.py:33
msgid "Такого бота нет в системе"
msgstr "Такого бота немає в системі"
#: olgram/commands/admin.py:39 olgram/commands/admin.py:53
msgid "Пропустить"
msgstr "Пропустити"
#: olgram/commands/admin.py:42
msgid ""
"Введите текст, который будет отправлен владельцу бота {0}. Напишите "
"'Пропустить' чтобы отменить"
msgstr ""
"Введіть текст, який буде надіслано власнику бота {0}. Напишіть 'Пропустити', "
"щоб скасувати"
#: olgram/commands/admin.py:50
msgid "Поддерживается только текст"
msgstr "Підтримується лише текст"
#: olgram/commands/admin.py:55 olgram/commands/admin.py:71
msgid "Отменено"
msgstr "Скасовано"
#: olgram/commands/admin.py:61 olgram/commands/admin.py:69
msgid "Отправить"
msgstr "Надіслати"
#: olgram/commands/admin.py:62
msgid "Отменить"
msgstr "Скасувати"
#: olgram/commands/admin.py:81
msgid "Отправлено"
msgstr "Надіслано"
#: olgram/commands/bot_actions.py:22
msgid "Бот удалён"
msgstr "Бот видалений"
#: olgram/commands/bot_actions.py:37 olgram/commands/bot_actions.py:49
#: olgram/commands/bot_actions.py:38 olgram/commands/bot_actions.py:50
msgid "Текст сброшен"
msgstr "Текст скинутий"
#: olgram/commands/bot_actions.py:63
#: olgram/commands/bot_actions.py:64
msgid "Выбран личный чат"
msgstr "Вибраний особистий чат"
#: olgram/commands/bot_actions.py:68
msgid "Нельзя привязать бота к этому чату"
msgstr "Нельзя привязать бота к этому чату"
#: olgram/commands/bot_actions.py:77
msgid "Бот вышел из чатов"
msgstr "Бот вийшов із чатів"
#: olgram/commands/bot_actions.py:72
#: olgram/commands/bot_actions.py:83
msgid "Нельзя привязать бота к этому чату"
msgstr "Не можна прив'язати робота до цього чату"
#: olgram/commands/bot_actions.py:87
msgid "Выбран чат {0}"
msgstr "Вибраний чат {0}"
#: olgram/commands/bots.py:42
#: olgram/commands/bots.py:46
msgid ""
"У вас уже слишком много ботов. Удалите какой-нибудь свой бот из Olgram(/"
"mybots -> (Выбрать бота) -> Удалить бот)"
@@ -46,7 +95,7 @@ msgstr ""
"У вас вже надто багато роботів. Видаліть якийсь свій бот з Olgram(/mybots -> "
"(Вибрати бота) -> Видалити бот)"
#: olgram/commands/bots.py:46
#: olgram/commands/bots.py:50
msgid ""
"\n"
" Чтобы подключить бот, вам нужно выполнить три действия:\n"
@@ -75,7 +124,7 @@ msgstr ""
" \n"
" "
#: olgram/commands/bots.py:66
#: olgram/commands/bots.py:70
msgid ""
"\n"
" Это не токен бота.\n"
@@ -91,7 +140,7 @@ msgstr ""
"abc123_AbcdEFghijKLMnopqrstu12\n"
" "
#: olgram/commands/bots.py:73
#: olgram/commands/bots.py:77
msgid ""
"\n"
" Не удалось запустить этого бота: неверный токен\n"
@@ -101,7 +150,7 @@ msgstr ""
" Не вдалося запустити цього бота: неправильний токен\n"
" "
#: olgram/commands/bots.py:78
#: olgram/commands/bots.py:82
msgid ""
"\n"
" Не удалось запустить этого бота: непредвиденная ошибка\n"
@@ -111,7 +160,7 @@ msgstr ""
" Не вдалося запустити цього бота: непередбачена помилка\n"
" "
#: olgram/commands/bots.py:83
#: olgram/commands/bots.py:87
msgid ""
"\n"
" Такой бот уже есть в базе данных\n"
@@ -121,35 +170,34 @@ msgstr ""
" Такий бот вже є у базі даних\n"
" "
#: olgram/commands/bots.py:115
#: olgram/commands/bots.py:122
msgid "Бот добавлен! Список ваших ботов: /mybots"
msgstr "Бот доданий! Список ваших роботів: /mybots"
#: olgram/commands/info.py:21 olgram/commands/promo.py:23
#: olgram/commands/promo.py:39
msgid "Недостаточно прав"
msgstr "Недостатньо прав"
#: olgram/commands/info.py:32
#: olgram/commands/info.py:34
msgid "Количество ботов: {0}\n"
msgstr "Кількість ботів: {0}\n"
#: olgram/commands/info.py:33
#: olgram/commands/info.py:35
msgid "Количество пользователей (у конструктора): {0}\n"
msgstr "Кількість користувачів (у конструктора): {0}\n"
#: olgram/commands/info.py:34
#: olgram/commands/info.py:36
msgid "Шаблонов ответов: {0}\n"
msgstr "Шаблонів відповідей: {0}\n"
#: olgram/commands/info.py:35
#: olgram/commands/info.py:37
msgid "Входящих сообщений у всех ботов: {0}\n"
msgstr "Вхідних повідомлень у всіх роботів: {0}\n"
#: olgram/commands/info.py:36
#: olgram/commands/info.py:38
msgid "Исходящих сообщений у всех ботов: {0}\n"
msgstr "Вихідних повідомлень у всіх роботів: {0}\n"
#: olgram/commands/info.py:39
msgid "Промо-кодов выдано: {0}\n"
msgstr "Промо-кодів видано: {0}\n"
#: olgram/commands/menu.py:31
msgid ""
"\n"
@@ -173,13 +221,17 @@ msgstr "Ваші боти"
msgid "Личные сообщения"
msgstr "Особисті повідомлення"
#: olgram/commands/menu.py:72 olgram/commands/menu.py:117
#: olgram/commands/menu.py:143 olgram/commands/menu.py:174
#: olgram/commands/menu.py:235
#: olgram/commands/menu.py:72
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
msgid "<< Назад"
msgstr "<< Назад"
#: olgram/commands/menu.py:78
#: olgram/commands/menu.py:83
msgid ""
"\n"
" Этот бот не добавлен в чаты, поэтому все сообщения будут приходить "
@@ -202,7 +254,7 @@ msgstr ""
" \n"
" "
#: olgram/commands/menu.py:85
#: olgram/commands/menu.py:90
msgid ""
"\n"
" В этом разделе вы можете привязать бота @{0} к чату.\n"
@@ -215,27 +267,27 @@ msgstr ""
" \n"
" "
#: olgram/commands/menu.py:97
#: olgram/commands/menu.py:102
msgid "Текст"
msgstr "Текст"
#: olgram/commands/menu.py:102
#: olgram/commands/menu.py:107
msgid "Чат"
msgstr "Чат"
#: olgram/commands/menu.py:107
#: olgram/commands/menu.py:112
msgid "Удалить бот"
msgstr "Видалити бот"
#: olgram/commands/menu.py:112
#: olgram/commands/menu.py:117
msgid "Статистика"
msgstr "Статистика"
#: olgram/commands/menu.py:121
#: olgram/commands/menu.py:126
msgid "Опции"
msgstr "Опції"
#: olgram/commands/menu.py:126
#: olgram/commands/menu.py:131
msgid ""
"\n"
" Управление ботом @{0}.\n"
@@ -253,11 +305,11 @@ msgstr ""
" @civsocit_feedback_bot\n"
" "
#: olgram/commands/menu.py:138
#: olgram/commands/menu.py:143
msgid "Да, удалить бот"
msgstr "Так, видалити бот"
#: olgram/commands/menu.py:147
#: olgram/commands/menu.py:152
msgid ""
"\n"
" Вы уверены, что хотите удалить бота @{0}?\n"
@@ -267,68 +319,87 @@ msgstr ""
" Ви впевнені, що хочете видалити бота @{0}?\n"
" "
#: olgram/commands/menu.py:156
#: olgram/commands/menu.py:161
msgid "Потоки сообщений"
msgstr "Потоки повідомлень"
#: olgram/commands/menu.py:161
#: olgram/commands/menu.py:166
msgid "Данные пользователя"
msgstr "Дані користувача"
#: olgram/commands/menu.py:168
#: olgram/commands/menu.py:171
msgid "Антифлуд"
msgstr "Антифлуд"
#: olgram/commands/menu.py:178
msgid "Olgram подпись"
msgstr "Olgram підпис"
#: olgram/commands/menu.py:179 olgram/commands/menu.py:180
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
msgid "включены"
msgstr "включені"
#: olgram/commands/menu.py:179 olgram/commands/menu.py:180
#: olgram/commands/menu.py:189 olgram/commands/menu.py:190
msgid "выключены"
msgstr "вимкнені"
#: olgram/commands/menu.py:181
#: olgram/commands/menu.py:191
#, fuzzy
#| msgid "включены"
msgid "включен"
msgstr "включені"
#: olgram/commands/menu.py:191
#, fuzzy
#| msgid "выключены"
msgid "выключен"
msgstr "вимкнені"
#: olgram/commands/menu.py:192
msgid ""
"\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#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\">Антифлуд</a>: <b>{2}</b>\n"
" "
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#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:187
#: olgram/commands/menu.py:199
msgid "включена"
msgstr "включена"
#: olgram/commands/menu.py:187
#: olgram/commands/menu.py:199
msgid "выключена"
msgstr "выключена"
msgstr "вимкнена"
#: olgram/commands/menu.py:188
#: olgram/commands/menu.py:200
msgid "Olgram подпись: <b>{0}</b>"
msgstr "Olgram підпис: <b>{0}</b>"
#: olgram/commands/menu.py:198 olgram/commands/menu.py:260
#: olgram/commands/menu.py:302
#: olgram/commands/menu.py:210 olgram/commands/menu.py:272
#: olgram/commands/menu.py:314
msgid "<< Завершить редактирование"
msgstr "<< Завершити редагування"
#: olgram/commands/menu.py:202
#: olgram/commands/menu.py:214
msgid "Автоответчик"
msgstr "Автовідповідач"
#: olgram/commands/menu.py:207 olgram/commands/menu.py:274
#: olgram/commands/menu.py:219 olgram/commands/menu.py:286
msgid "Сбросить текст"
msgstr "Скинути текст"
#: olgram/commands/menu.py:212
#: olgram/commands/menu.py:224
msgid ""
"\n"
" Сейчас вы редактируете текст, который отправляется после того, как "
@@ -355,7 +426,7 @@ msgstr ""
" \n"
" "
#: olgram/commands/menu.py:239
#: olgram/commands/menu.py:251
msgid ""
"\n"
" Статистика по боту @{0}\n"
@@ -375,15 +446,15 @@ msgstr ""
" Забанено користувачів: <b>{4}</b>\n"
" "
#: olgram/commands/menu.py:264
#: olgram/commands/menu.py:276
msgid "Предыдущий текст"
msgstr "Попередній текст"
#: olgram/commands/menu.py:269
#: olgram/commands/menu.py:281
msgid "Шаблоны ответов..."
msgstr "Шаблони відповідей..."
#: olgram/commands/menu.py:279
#: olgram/commands/menu.py:291
msgid ""
"\n"
" Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в "
@@ -409,11 +480,11 @@ msgstr ""
" Надішліть повідомлення, щоб змінити текст.\n"
" "
#: olgram/commands/menu.py:289
#: olgram/commands/menu.py:301
msgid "(отключено)"
msgstr "(відключено)"
#: olgram/commands/menu.py:306
#: olgram/commands/menu.py:318
msgid ""
"\n"
" Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n"
@@ -439,27 +510,27 @@ msgstr ""
" \n"
" "
#: olgram/commands/menu.py:325
#: olgram/commands/menu.py:337
msgid "(нет шаблонов)"
msgstr "(Немає шаблонів)"
#: olgram/commands/menu.py:364
#: olgram/commands/menu.py:376
msgid "У вас нет шаблонов, чтобы их удалять"
msgstr "У вас немає шаблонів, щоб їх видаляти"
#: olgram/commands/menu.py:366
#: olgram/commands/menu.py:378
msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}"
msgstr "Неправильне число. Щоб видалити шаблон, введіть число від 0 до {0}"
#: olgram/commands/menu.py:374
#: olgram/commands/menu.py:386
msgid "У вашего бота уже слишком много шаблонов"
msgstr "У вашого бота вже дуже багато шаблонів"
#: olgram/commands/menu.py:378
#: olgram/commands/menu.py:390
msgid "Такой текст уже есть в списке шаблонов"
msgstr "Такий текст вже є у списку шаблонів"
#: olgram/commands/menu.py:396
#: olgram/commands/menu.py:408
msgid "У вас нет прав на этого бота"
msgstr "У вас немає прав на цього бота"
@@ -479,25 +550,25 @@ msgstr "Неправильний токен"
msgid "Такого кода не существует"
msgstr "Такого коду не існує"
#: olgram/commands/promo.py:53
#: olgram/commands/promo.py:59
msgid "Промокод отозван"
msgstr "Промокод відкликаний"
#: olgram/commands/promo.py:64
#: olgram/commands/promo.py:70
msgid ""
"Укажите аргумент: промокод. Например: <pre>/set_promo my-promo-code</pre>"
"Укажите аргумент: промокод. Например: <pre>/setpromo my-promo-code</pre>"
msgstr ""
"Зазначте аргумент: промокод. Наприклад: <pre>/set_promo my-promo-code</pre>"
"Зазначте аргумент: промокод. Наприклад: <pre>/setpromo my-promo-code</pre>"
#: olgram/commands/promo.py:72 olgram/commands/promo.py:76
#: olgram/commands/promo.py:78 olgram/commands/promo.py:82
msgid "Промокод не найден"
msgstr "Промокод не знайдено"
#: olgram/commands/promo.py:79
#: olgram/commands/promo.py:85
msgid "Промокод уже использован"
msgstr "Промокод уже використаний"
#: olgram/commands/promo.py:85
#: olgram/commands/promo.py:91
msgid "Промокод активирован! Спасибо 🙌"
msgstr "Промокод активовано! Дякую 🙌"
@@ -505,7 +576,8 @@ msgstr "Промокод активовано! Дякую 🙌"
msgid ""
"\n"
" Olgram Bot — это конструктор ботов обратной связи в Telegram. Подробнее "
"<a href=\"https://olgram.readthedocs.io\">читайте здесь</a>.\n"
"<a href=\"https://olgram.readthedocs.io\">читайте здесь</a>. Следите за "
"обновлениями <a href=\"https://t.me/civsoc_it\">здесь</a>.\n"
"\n"
" Используйте эти команды, чтобы управлять этим ботом:\n"
"\n"
@@ -517,7 +589,8 @@ msgid ""
msgstr ""
"\n"
" Olgram Bot - це конструктор роботів зворотного зв'язку в Telegram. "
"Докладніше <a href=\"https://olgram.readthedocs.io\">читайте тут</a>.\n"
"Докладніше <a href=\"https://olgram.readthedocs.io\">читайте тут</a>. "
"Слідкуйте за оновленнями <a href=\"https://t.me/civsoc_it\">тут</a>.\n"
"\n"
" Використовуйте ці команди, щоб керувати цим ботом:\n"
"\n"
@@ -528,7 +601,7 @@ msgstr ""
" \n"
" "
#: olgram/commands/start.py:42
#: olgram/commands/start.py:43
msgid ""
"\n"
" Читайте инструкции на нашем сайте https://olgram.readthedocs.io\n"
@@ -564,7 +637,7 @@ msgstr "Власник бота обмежив доступ до цього фу
msgid "Владелец бота ограничил доступ к этому функционалу😞"
msgstr "Власник бота обмежив доступ до цього функціоналу 😞"
#: server/custom.py:40
#: server/custom.py:55
msgid ""
"<b>Политика конфиденциальности</b>\n"
"\n"
@@ -584,7 +657,7 @@ msgstr ""
"з оператором; боти Olgram не роблять масових розсилок.\n"
"\n"
#: server/custom.py:46
#: server/custom.py:61
msgid ""
"При отправке сообщения (кроме команд /start и /security_policy) оператор "
"<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в "
@@ -594,7 +667,7 @@ msgstr ""
"оператор <b>бачить</b> ваше ім'я користувача, @username та ідентифікатор "
"користувача через налаштування, які оператор вказав при створенні бота."
#: server/custom.py:50
#: server/custom.py:65
msgid ""
"В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
"видеть ваш username, имя пользователя и другую информацию."
@@ -602,15 +675,23 @@ msgstr ""
"Залежно від ваших налаштувань конфіденційності Telegram оператор може бачити "
"ваш username, ім'я користувача та іншу інформацію."
#: server/custom.py:61
#: server/custom.py:76
msgid "Сообщение от пользователя "
msgstr "Допис від користувача "
#: server/custom.py:88
#: server/custom.py:135
msgid "Вы заблокированы в этом боте"
msgstr "Ви заблоковані у цьому боті"
#: server/custom.py:132
#: server/custom.py:141
msgid "Слишком много сообщений, подождите одну минуту"
msgstr "Забагато повідомлень, зачекайте одну хвилину"
#: server/custom.py:148
msgid "Не удаётся связаться с владельцем бота"
msgstr "Не вдається зв'язатися з власником бота"
#: server/custom.py:179
msgid ""
"<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
"старое?)</i>"
@@ -618,19 +699,19 @@ msgstr ""
"<i>Неможливо надіслати повідомлення: автора не знайдено (повідомлення "
"занадто старе?)</i>"
#: server/custom.py:140
#: server/custom.py:187
msgid "Пользователь заблокирован"
msgstr "Користувач заблоковано"
#: server/custom.py:145
#: server/custom.py:192
msgid "Пользователь не был забанен"
msgstr "Користувач не був забанений"
#: server/custom.py:148
#: server/custom.py:195
msgid "Пользователь разбанен"
msgstr "Користувач розбанений"
#: server/custom.py:153
#: server/custom.py:200
msgid "<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"
msgstr "<i>Неможливо надіслати повідомлення (автор заблокував робота?)</i>"
@@ -642,21 +723,11 @@ msgstr "(Пере) запустити бота"
msgid "Политика конфиденциальности"
msgstr "Політика конфіденційності"
msgid ""
"\n"
"\n"
"Этот бот создан с помощью @OlgramBot"
msgstr ""
"\n"
"\n"
"Цей бот створено за допомогою @OlgramBot"
msgid "Не удаётся связаться с владельцем бота"
msgstr "Не вдається зв'язатися з власником бота"
msgid "Слишком много сообщений, подождите одну минуту"
msgstr "Забагато повідомлень, зачекайте одну хвилину"
msgid "Антифлуд"
msgstr "Антифлуд"
#~ msgid ""
#~ "\n"
#~ "\n"
#~ "Этот бот создан с помощью @OlgramBot"
#~ msgstr ""
#~ "\n"
#~ "\n"
#~ "Цей бот створено за допомогою @OlgramBot"

View File

@@ -25,7 +25,7 @@ async def init_database():
async def init_olgram():
from olgram.router import bot, dp
dp.setup_middleware(AccessMiddleware(OlgramSettings.admin_id()))
dp.setup_middleware(AccessMiddleware(OlgramSettings.admin_ids()))
from aiogram.types import BotCommand
await bot.set_my_commands(
[

View File

@@ -1,9 +1,13 @@
"""
Здесь работа с конкретным ботом
"""
from asyncio import sleep
from datetime import datetime
from aiogram import types
from aiogram.utils.exceptions import TelegramAPIError, Unauthorized
from aiogram.utils import exceptions
from aiogram import Bot as AioBot
from olgram.models.models import Bot
from olgram.utils.mix import send_stored_message
from server.server import unregister_token
from locales.locale import _
@@ -14,14 +18,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
@@ -62,6 +66,20 @@ async def select_chat(bot: Bot, call: types.CallbackQuery, chat: str):
await bot.save()
await call.answer(_("Выбран личный чат"))
return
if chat == "leave":
bot.group_chat = None
await bot.save()
chats = await bot.group_chats.all()
a_bot = AioBot(bot.decrypted_token())
for chat in chats:
try:
await chat.delete()
await a_bot.leave_chat(chat.chat_id)
except exceptions.TelegramAPIError:
pass
await call.answer(_("Бот вышел из чатов"))
await a_bot.session.close()
return
chat_obj = await bot.group_chats.filter(id=chat).first()
if not chat_obj:
@@ -91,3 +109,35 @@ async def olgram_text(bot: Bot, call: types.CallbackQuery):
async def antiflood(bot: Bot, call: types.CallbackQuery):
bot.enable_antiflood = not bot.enable_antiflood
await bot.save(update_fields=["enable_antiflood"])
async def mailing(bot: Bot, call: types.CallbackQuery):
bot.enable_mailing = not bot.enable_mailing
await bot.save(update_fields=["enable_mailing"])
async def go_mailing(bot: Bot, context) -> int:
users = await bot.mailing_users
a_bot = AioBot(bot.decrypted_token())
count = 0
print(f"start mailing {context}")
for user in users:
bot.last_mailing_at = datetime.now()
await bot.save(update_fields=["last_mailing_at"])
try:
await sleep(0.05)
try:
await send_stored_message(context, a_bot, user.telegram_id)
except exceptions.RetryAfter as err:
await sleep(err.timeout)
await send_stored_message(context, a_bot, user.telegram_id)
count += 1
except (exceptions.ChatNotFound, exceptions.BotBlocked, exceptions.UserDeactivated):
await user.delete()
except exceptions.TelegramAPIError:
pass
return count

View File

@@ -37,4 +37,4 @@ async def info(message: types.Message, state: FSMContext):
_("Входящих сообщений у всех ботов: {0}\n").format(income_messages) +
_("Исходящих сообщений у всех ботов: {0}\n").format(outgoing_messages) +
_("Промо-кодов выдано: {0}\n").format(promo_count) +
_("Рекламную плашку выключили: {0}\n".format(olgram_text_disabled)))
_("Рекламную плашку выключили: {0}\n").format(olgram_text_disabled))

View File

@@ -4,14 +4,14 @@ from aiogram import types, Bot as AioBot
from olgram.models.models import Bot, User, DefaultAnswer
from aiogram.dispatcher import FSMContext
from aiogram.utils.callback_data import CallbackData
from datetime import datetime, timedelta
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 _
import typing as ty
menu_callback = CallbackData('menu', 'level', 'bot_id', 'operation', 'chat')
empty = "0"
@@ -68,6 +68,11 @@ async def send_chats_menu(bot: Bot, call: types.CallbackQuery):
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="chat",
chat="personal"))
)
keyboard.insert(
types.InlineKeyboardButton(text=_("❗️ Выйти из всех чатов"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="chat",
chat="leave"))
)
keyboard.insert(
types.InlineKeyboardButton(text=_("<< Назад"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty,
@@ -122,6 +127,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}.
@@ -151,7 +162,7 @@ async def send_bot_delete_menu(bot: Bot, call: types.CallbackQuery):
async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery):
await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=1)
keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert(
types.InlineKeyboardButton(text=_("Потоки сообщений"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="threads",
@@ -167,6 +178,11 @@ async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery):
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="antiflood",
chat=empty))
)
keyboard.insert(
types.InlineKeyboardButton(text=_("Рассылка"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="mailing",
chat=empty))
)
is_promo = await bot.is_promo()
if is_promo:
keyboard.insert(
@@ -184,11 +200,13 @@ async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery):
thread_turn = _("включены") if bot.enable_threads else _("выключены")
info_turn = _("включены") if bot.enable_additional_info else _("выключены")
antiflood_turn = _("включен") if bot.enable_antiflood else _("выключен")
mailing_turn = _("включена") if bot.enable_mailing else _("выключена")
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#user-info">Данные пользователя</a>: <b>{1}</b>
<a href="https://olgram.readthedocs.io/ru/latest/options.html#antiflood">Антифлуд</a>: <b>{2}</b>
""")).format(thread_turn, info_turn, antiflood_turn)
<a href="https://olgram.readthedocs.io/ru/latest/options.html#mailing">Рассылка</a>: <b>{3}</b>
""")).format(thread_turn, info_turn, antiflood_turn, mailing_turn)
if is_promo:
olgram_turn = _("включена") if bot.enable_olgram_text else _("выключена")
@@ -233,6 +251,30 @@ 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")
async def send_bot_statistic_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] = None,
chat_id: ty.Optional[int] = None):
if call:
@@ -347,6 +389,49 @@ async def start_text_received(message: types.Message, state: FSMContext):
await send_bot_text_menu(bot, chat_id=message.chat.id)
@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
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
async def second_text_received(message: types.Message, state: FSMContext):
async with state.proxy() as proxy:
@@ -418,6 +503,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() - 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:
@@ -435,6 +529,9 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
if operation == "antiflood":
await bot_actions.antiflood(bot, call)
return await send_bot_settings_menu(bot, call)
if operation == "mailing":
await bot_actions.mailing(bot, call)
return await send_bot_settings_menu(bot, call)
if operation == "additional_info":
await bot_actions.additional_info(bot, call)
return await send_bot_settings_menu(bot, call)
@@ -449,6 +546,20 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
async with state.proxy() as proxy:
proxy["bot_id"] = bot.id
return await send_bot_second_text_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() - 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":
await bot_actions.reset_bot_second_text(bot, call)
return await send_bot_second_text_menu(bot, call)

View File

@@ -60,14 +60,14 @@ async def del_promo(message: types.Message, state: FSMContext):
@dp.message_handler(commands=["setpromo"], state="*")
async def set_promo(message: types.Message, state: FSMContext):
async def setpromo(message: types.Message, state: FSMContext):
"""
Команда /setpromo
"""
arg = message.get_args()
if not arg:
return await message.answer(_("Укажите аргумент: промокод. Например: <pre>/set_promo my-promo-code</pre>"),
return await message.answer(_("Укажите аргумент: промокод. Например: <pre>/setpromo my-promo-code</pre>"),
parse_mode="HTML")
arg = arg.strip()

View File

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

View File

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

View File

@@ -41,13 +41,13 @@ class OlgramSettings(AbstractSettings):
@classmethod
def version(cls):
return "0.4.3"
return "0.5.0"
@classmethod
@lru_cache
def admin_id(cls):
_id = cls._get_env("ADMIN_ID", True)
return int(_id) if _id else None
def admin_ids(cls):
_ids = cls._get_env("ADMIN_ID", True)
return set(map(int, _ids.split(","))) if _ids else None
@classmethod
@lru_cache
@@ -105,7 +105,7 @@ class ServerSettings(AbstractSettings):
return int(timedelta(days=1).total_seconds() * 1000.0)
logging.basicConfig(level=os.environ.get("LOGLEVEL", "WARNING"),
logging.basicConfig(level=os.environ.get("LOGLEVEL") or "WARNING",
format='%(asctime)s %(levelname)-8s %(message)s')

View File

@@ -1,4 +1,5 @@
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup
from aiogram import types, Bot as AioBot
from aiogram.utils.exceptions import TelegramAPIError
from typing import Optional
@@ -30,3 +31,23 @@ def wrap(data: str, max_len: int) -> str:
def button_text_limit(data: str) -> str:
return wrap(data, 30)
async def send_stored_message(storage: dict, bot: AioBot, chat_id: int):
content_type = storage["mailing_content_type"]
if content_type == types.ContentType.TEXT:
return await bot.send_message(chat_id, storage["mailing_text"], parse_mode="HTML")
if content_type == types.ContentType.LOCATION:
return await bot.send_location(chat_id, storage["mailing_location"][0], storage["mailing_location"][1])
if content_type == types.ContentType.AUDIO:
return await bot.send_audio(chat_id, audio=storage["mailing_audio"], caption=storage.get("mailing_caption"))
if content_type == types.ContentType.DOCUMENT:
return await bot.send_document(chat_id, document=storage["mailing_document"],
caption=storage.get("mailing_caption"))
if content_type == types.ContentType.PHOTO:
return await bot.send_photo(chat_id, photo=storage["mailing_photo"],
caption=storage.get("mailing_caption"))
if content_type == types.ContentType.VIDEO:
return await bot.send_video(chat_id, video=storage["mailing_video"],
caption=storage.get("mailing_caption"))
raise NotImplementedError("Mailing, unknown content type")

View File

@@ -1,6 +1,7 @@
import aiogram.types as types
from aiogram.dispatcher.handler import CancelHandler, current_handler
from aiogram.dispatcher.middlewares import BaseMiddleware
import typing as ty
from locales.locale import _
@@ -19,8 +20,8 @@ def public():
class AccessMiddleware(BaseMiddleware):
def __init__(self, access_chat_id: int):
self._access_chat_id = access_chat_id
def __init__(self, access_chat_ids: ty.Iterable[int]):
self._access_chat_ids = access_chat_ids
super(AccessMiddleware, self).__init__()
@classmethod
@@ -29,25 +30,25 @@ class AccessMiddleware(BaseMiddleware):
return handler and getattr(handler, "access_public", False)
async def on_process_message(self, message: types.Message, data: dict):
admin_id = self._access_chat_id
if not admin_id:
return # Администратор бота вообще не указан
admin_ids = self._access_chat_ids
if not admin_ids:
return # Администраторы бота вообще не указаны
if self._is_public_command(): # Эта команда разрешена всем пользователям
return
if message.chat.id != admin_id:
if message.chat.id not in admin_ids:
await message.answer(_("Владелец бота ограничил доступ к этому функционалу 😞"))
raise CancelHandler()
async def on_process_callback_query(self, call: types.CallbackQuery, data: dict):
admin_id = self._access_chat_id
if not admin_id:
return # Администратор бота вообще не указан
admin_ids = self._access_chat_ids
if not admin_ids:
return # Администраторы бота вообще не указаны
if self._is_public_command(): # Эта команда разрешена всем пользователям
return
if call.message.chat.id != admin_id:
if call.message.chat.id not in admin_ids:
await call.answer(_("Владелец бота ограничил доступ к этому функционалу😞"))
raise CancelHandler()

View File

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