From 92bb50dd49d37594655b39dbe1f68e5dca5fe587 Mon Sep 17 00:00:00 2001 From: kotolex Date: Tue, 26 Dec 2023 16:58:27 +0500 Subject: [PATCH] FEATURE 14: autoreaction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - если фулл_нейм пустой то будет сообщение о нарушении правила, само сообщение юзера будет удалено - добавлена правка по спаму - немного рефакторинга - если сообщение содержит только слово из констант привет(полное совпадение) то выдаем правило непривет --- src/admin_commands.py | 20 +++++--------------- src/commands.py | 35 ++++++++++++++++++++++++++--------- src/constants.py | 3 ++- src/filters.py | 13 +++++++++++-- src/helpers.py | 13 ++++++++++--- tests/test_bot.py | 27 +++++++++++++++++++++------ tests/test_helpers.py | 27 ++++++++++++++++++++++++--- 7 files changed, 99 insertions(+), 39 deletions(-) diff --git a/src/admin_commands.py b/src/admin_commands.py index 32530b6..f79bd16 100644 --- a/src/admin_commands.py +++ b/src/admin_commands.py @@ -78,9 +78,7 @@ async def enable_stan(message: types.Message): add_chat(message.chat.id, message.chat.title) await bot.send_message( message.chat.id, - f"""Группа "{message.chat.title}" добавлена в БД. -/get_group_info - узнать текущие настройки""", - ) + f'Группа "{message.chat.title}" добавлена в БД.\n/get_group_info - узнать текущие настройки') else: await bot.send_message(message.chat.id, "Отказ. Группа уже включена") @@ -119,7 +117,7 @@ async def get_quotes(message: types.Message): logging.info(f"[{message.chat.id}] {message.from_user.first_name} {message.text}.") quotes = all_chat_quotes(message.chat.id) if quotes: - text = f"Количество цитат: {len(quotes)}\n\Последние добавленные\n\n" + text = f"Количество цитат: {len(quotes)}\n\nПоследние добавленные:\n\n" text += "\n".join(f"· {quote}" for quote in quotes[-10:]) await bot.send_message(message.chat.id, f"{text}") else: @@ -132,17 +130,9 @@ async def get_group_info(message: types.Message): if group: await bot.send_message( message.chat.id, - f"""Группа: {group.title} -ID группы: {group.chat_id} - -Количество цитат: {len(group.quotes)} -Последние добавленные: /get_quotes - -Текущие настройки: - Антиспам: {group.antispam} - Ежедневные отчёты: {group.report} - Праздники: {group.reminder}""", - ) + f"Группа: {group.title}\nID группы: {group.chat_id}\n\nКоличество цитат: {len(group.quotes)}\n" + f"Последние добавленные: /get_quotes\n\nТекущие настройки:\n Антиспам: {group.antispam}\n " + f"Ежедневные отчёты: {group.report}\n Праздники: {group.reminder}") else: await bot.send_message(message.chat.id, "Группа не включена. Включить: /enable_stan") diff --git a/src/commands.py b/src/commands.py index 1edec03..623c4e6 100644 --- a/src/commands.py +++ b/src/commands.py @@ -6,7 +6,7 @@ from telebot import types from .admin_commands import bot from .constants import (LOG_COMM, FAQ, LIB, RULES, RUS, RUS_ENG_TABLE, ENG_RUS_TABLE, PYTHONCHATRU, ZEN, LUTZ_ID, BDMTSS_ID) -from .filters import in_spam_list, in_caption_spam_list, in_delete_list +from .filters import in_spam_list, in_caption_spam_list, in_delete_list, is_hello_text, is_empty_name from .helpers import represent_as_get, detect_args, is_admin, fetch_rule from .report import update_stats, increment from .stan import act, speak @@ -19,6 +19,16 @@ async def send_or_reply(message: types.Message, answer, **kwargs): await bot.send_message(message.chat.id, answer, **kwargs) +async def _neprivet(message: types.Message, forced_reply: bool = False): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton("👋 Непривет", url="https://neprivet.com/"), row_width=1) + text = "Пожалуйста, не пишите просто «Привет» в чате." + if forced_reply: + await bot.reply_to(message, text, reply_markup=markup) + else: + await send_or_reply(message, text, reply_markup=markup) + + """ [ ANTISPAM ] """ @@ -66,11 +76,7 @@ async def send_rules(message: types.Message): logging.info(LOG_COMM % (message.chat.title, message.from_user.id, message.from_user.first_name, message.text)) if len(args) > 1 and args[-1].isdigit(): index = int(args[-1]) - await send_or_reply( - message, - f"Правило {args[-1]}\n{fetch_rule(index)}", - reply_markup=markup, - ) + await send_or_reply(message, f"Правило {index}\n{fetch_rule(index)}", reply_markup=markup) else: await send_or_reply(message, "Читай...", reply_markup=markup) await bot.delete_message(message.chat.id, message.id) @@ -225,9 +231,7 @@ async def send_neprivet(message: types.Message): message.text, ) ) - markup = types.InlineKeyboardMarkup() - markup.add(types.InlineKeyboardButton("👋 Непривет", url="https://neprivet.com/"), row_width=1) - await send_or_reply(message, "Пожалуйста, не пишите просто «Привет» в чате.", reply_markup=markup) + await _neprivet(message) await bot.delete_message(message.chat.id, message.id) @@ -348,6 +352,19 @@ async def default_query(inline_query): await bot.answer_inline_query(inline_query.id, zen, cache_time=1200) +@bot.message_handler(func=is_empty_name, chat_types=["supergroup", "group"]) +async def handle_hello(message: types.Message): + await bot.delete_message(message.chat.id, message.id) + nick_rule = 6 + await bot.send_message(message.chat.id, f"@{message.from_user.username}\n" + f"Напоминаем правило: {fetch_rule(nick_rule)}") + + +@bot.message_handler(func=is_hello_text, chat_types=["supergroup", "group"]) +async def handle_hello(message: types.Message): + await _neprivet(message, forced_reply=True) + + """ [ COUNTER ] """ diff --git a/src/constants.py b/src/constants.py index 690b882..82c063c 100644 --- a/src/constants.py +++ b/src/constants.py @@ -35,6 +35,7 @@ ALLOWED_WORDS = [ "support", "jetbrains", ] +HELLO_EXAMPLES = ('привет', 'привет всем', 'ку', 'здравствуйте', 'прив', 'всем прив', 'ребята всем привет', 'прива') ONLY_RUS_LETTERS = "ёйцукенгшщзхъфывапролджэячсмитьбю" ONLY_ENG_LETTERS = string.ascii_lowercase @@ -59,7 +60,7 @@ SPAM = {"me.sv/", "tg.sv/", "goo.by/", "go.sv/", "intim.video/", "uclck.ru/", ' 'P2P', 'Идет набор', 'в лс', 'нaбop', 'комaндy', 'kpuпт', 'бupж', '💰', 'пишитe', '💸', '$ в неделю', 'обучаю с нуля', 'доходы для всех', 'по трейдингу', 'фальшивые рубли', '‼️', 'зароботку', 'Набuраю', 'цuфр', 'Обучу вас', 'прuб', 'остались курсы', 'Crypt', 'kр', 'рu', - 'в лc', "в личные сообщения"} + 'в лc', "в личные сообще"} NON_GRATA = {"дудар", "хауди", "dudar"} BAN_WORDS = {"GREEN"} diff --git a/src/filters.py b/src/filters.py index 87f68bb..2ca0be2 100644 --- a/src/filters.py +++ b/src/filters.py @@ -3,8 +3,8 @@ import logging.handlers from telebot import types from .config import WHITEIDS -from .constants import LOG_COMM, ALLOWED_WORDS, URL_RX -from .helpers import is_spam, is_in_not_allowed, is_ban_words_in_caption +from .constants import LOG_COMM, ALLOWED_WORDS, URL_RX, HELLO_EXAMPLES +from .helpers import is_spam, is_in_not_allowed, is_ban_words_in_caption, cleaned_text, remove_spaces from .models import is_antispam_enabled @@ -45,3 +45,12 @@ def in_delete_list(message: types.Message) -> bool: def is_white_id(message: types.Message) -> bool: return message.from_user.id in WHITEIDS + + +def is_hello_text(message: types.Message) -> bool: + text = remove_spaces(cleaned_text(message.text)) + return any(hello == text for hello in HELLO_EXAMPLES) + + +def is_empty_name(message: types.Message) -> bool: + return message.from_user.full_name.strip() == '' diff --git a/src/helpers.py b/src/helpers.py index 612532d..c09bed5 100644 --- a/src/helpers.py +++ b/src/helpers.py @@ -30,9 +30,8 @@ def is_mixed(text: str) -> bool: Разбивает текст на слова и проверяет на смешанный алфавит, спамеры используют его. Слово допускается только полностью на русском или русский + знаки пунктуации """ - cleaned_text = ''.join(letter if letter not in SYMBOLS else ' ' for letter in text) - for word in cleaned_text.split(): - word = word.strip().lower() + for word in cleaned_text(text).split(): + word = word.strip() if not word: continue if any(e in ONLY_RUS_LETTERS for e in word) and any(e in ONLY_ENG_LETTERS for e in word): @@ -40,6 +39,14 @@ def is_mixed(text: str) -> bool: return False +def cleaned_text(text: str) -> str: + return ''.join(letter.lower() if letter not in SYMBOLS else ' ' for letter in text) + + +def remove_spaces(text: str) -> str: + return ' '.join(word.strip() for word in text.split() if word.strip()) + + def is_in_not_allowed(word_list: list, msg: str) -> bool: for word in word_list: if word in msg.casefold(): diff --git a/tests/test_bot.py b/tests/test_bot.py index a5d61c7..35bbafd 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -17,7 +17,7 @@ from src import helpers # нужно не дать делать реальные запросы в тестах, конкретно тут -за Правилами чата helpers.is_url_reachable = lambda a: True from src.commands import bot -from src.constants import BDMTSS_ID, LUTZ_ID, ADMIN_ID +from src.constants import BDMTSS_ID, LUTZ_ID, ADMIN_ID, HELLO_EXAMPLES from src.filters import is_white_id # отключаем логи бота, в тестах они не нужны @@ -45,11 +45,11 @@ def _all_nones(): shipping_query, pre_checkout_query, poll, poll_answer, my_chat_member, chat_member, chat_join_request) -def get_update(text, reply_to=None, user_id=10): +def get_update(text, reply_to=None, user_id=10, first_name='Some User'): """Хелпер, генерирующий событие обновления для бота""" params = {'text': text} chat = types.Chat(id=11, type='group') - user = types.User(id=user_id, is_bot=False, first_name='Some User') + user = types.User(id=user_id, is_bot=False, first_name=first_name, full_name=first_name) if reply_to: params["reply_to_message"] = types.Message(message_id=2, from_user=user, date=None, chat=chat, content_type='text', options={'text': reply_to}, json_string="") @@ -155,6 +155,15 @@ class TestBot(IsolatedAsyncioTestCase): await self.bot.process_new_updates([get_update('/neprivet', reply_to="привет всем")]) self.assertEqual(RESULTS[0][0]['text'], "Пожалуйста, не пишите просто «Привет» в чате.") self.assertTrue("https://neprivet.com/" in RESULTS[0][0]['reply_markup']) + self.assertEqual(RESULTS[1][1], "deleteMessage") + + async def test_neprivet_auto(self): + for hello in HELLO_EXAMPLES: + with self.subTest(f"neprivet auto {hello}"): + await self.bot.process_new_updates([get_update(hello)]) + self.assertEqual(RESULTS[0][0]['text'], "Пожалуйста, не пишите просто «Привет» в чате.") + self.assertTrue("https://neprivet.com/" in RESULTS[0][0]['reply_markup']) + RESULTS.clear() async def test_lutz(self): await self.bot.process_new_updates([get_update('/lutz', reply_to="привет всем")]) @@ -441,8 +450,8 @@ class TestBot(IsolatedAsyncioTestCase): async def test_get_quotes(self): params = ( ("Цитаты отсутствуют. Подробнее: /get_group_info", []), - ("Количество цитат: 1\n\\Последние добавленные\n\n· one", ['one']), - ("Количество цитат: 2\n\\Последние добавленные\n\n· one\n· two", ['one', 'two']), + ("Количество цитат: 1\n\nПоследние добавленные:\n\n· one", ['one']), + ("Количество цитат: 2\n\nПоследние добавленные:\n\n· one\n· two", ['one', 'two']), ) for expected, quotes in params: with self.subTest(f"get_quotes {quotes}"): @@ -453,7 +462,7 @@ class TestBot(IsolatedAsyncioTestCase): RESULTS.clear() async def test_get_group_info(self): - exp = f"Группа: Title\nID группы: 11 \n\nКоличество цитат: 0\nПоследние добавленные: /get_quotes\n\n" \ + exp = f"Группа: Title\nID группы: 11\n\nКоличество цитат: 0\nПоследние добавленные: /get_quotes\n\n" \ f"Текущие настройки:\n Антиспам: 1\n Ежедневные отчёты: 1\n Праздники: 1" params = ( ("Группа не включена. Включить: /enable_stan", None), @@ -467,6 +476,12 @@ class TestBot(IsolatedAsyncioTestCase): self.assertEqual(expected, RESULTS[0][0]['text']) RESULTS.clear() + async def test_empty_name_user(self): + await self.bot.process_new_updates([get_update('новый текст', first_name='')]) + self.assertEqual(RESULTS[0], ({'chat_id': 11, 'message_id': 1}, 'deleteMessage')) + self.assertEqual(RESULTS[1][1], 'sendMessage') + self.assertEqual(RESULTS[1][0]['text'], '@None\nНапоминаем правило: Твой ник должен быть читаемым и понятным') + if __name__ == '__main__': main() diff --git a/tests/test_helpers.py b/tests/test_helpers.py index a1540aa..4fd6c15 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,12 +1,14 @@ from unittest import TestCase, main from unittest.mock import MagicMock from src.constants import SPAM, BAN_WORDS, ADMIN_ID, RULES_TEXT -from src.helpers import is_spam, is_mixed, is_ban_words_in_caption, is_in_not_allowed, is_nongrata, is_admin, fetch_rule +from src.helpers import is_spam, is_mixed, is_ban_words_in_caption, is_in_not_allowed, is_nongrata, is_admin, \ + fetch_rule, cleaned_text, remove_spaces CASES = ( "Оформим резидeнтствo ОАЭ без предoплаты за 5000 дирхам. Пoдробнoсти в лc", "Всем привет Я в поисках людей в kpиптo кoмaнду У нас вы пройдете 6ecплaтноe обученue 1-3 дня Можно совмещать с основной paбoтoй", "Здравствуйте! Нужны 2 человека от 200 $/день. Можно без опыта Пишите в личные сообщения +", + "Здравствуйте!Нужны 2 человека от 200 $/день.Пишите в личные сообщение +" ) @@ -44,7 +46,6 @@ class TestSpam(TestCase): self.assertFalse(is_mixed("text='слово'")) - class TestOthers(TestCase): def test_ban(self): for word in BAN_WORDS: @@ -86,7 +87,7 @@ class TestOthers(TestCase): self.assertEqual(is_admin(m), expected) def test_fetch_rule(self): - for index, text in enumerate(RULES_TEXT,1): + for index, text in enumerate(RULES_TEXT, 1): with self.subTest(f"fetch rule {index}"): self.assertEqual(fetch_rule(index), text) @@ -94,6 +95,26 @@ class TestOthers(TestCase): self.assertEqual(fetch_rule(0), 'Пока не придумали') self.assertEqual(fetch_rule(100), 'Пока не придумали') + def test_cleaned_text(self): + params = ( + ('f1 b2 print text ', 'f1!, b2# print("text")'), + ('text', 'text'), + (' ', '!@#$'), + ) + for expected, text in params: + with self.subTest(f"cleaned_text {text}"): + self.assertEqual(expected, cleaned_text(text)) + + def test_remove_spaces(self): + params = ( + ('f1 b2 print text', 'f1 b2 print text '), + ('text', 'text'), + ('!@#$', '!@#$'), + ) + for expected, text in params: + with self.subTest(f"remove_spaces {text}"): + self.assertEqual(expected, remove_spaces(text)) + if __name__ == '__main__': main()