FEATURE 14: autoreaction

- если фулл_нейм пустой то будет сообщение о нарушении правила, само сообщение юзера будет удалено
- добавлена правка по спаму
- немного рефакторинга
- если сообщение содержит только слово из констант привет(полное совпадение) то выдаем правило непривет
This commit is contained in:
kotolex
2023-12-26 16:58:27 +05:00
parent 77e34681ec
commit 92bb50dd49
7 changed files with 99 additions and 39 deletions
+5 -15
View File
@@ -78,9 +78,7 @@ async def enable_stan(message: types.Message):
add_chat(message.chat.id, message.chat.title) add_chat(message.chat.id, message.chat.title)
await bot.send_message( await bot.send_message(
message.chat.id, message.chat.id,
f"""Группа "{message.chat.title}" добавлена в БД. f'Группа "{message.chat.title}" добавлена в БД.\n/get_group_info - узнать текущие настройки')
/get_group_info - узнать текущие настройки""",
)
else: else:
await bot.send_message(message.chat.id, "Отказ. Группа уже включена") 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}.") logging.info(f"[{message.chat.id}] {message.from_user.first_name} {message.text}.")
quotes = all_chat_quotes(message.chat.id) quotes = all_chat_quotes(message.chat.id)
if quotes: 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:]) text += "\n".join(f"· {quote}" for quote in quotes[-10:])
await bot.send_message(message.chat.id, f"{text}") await bot.send_message(message.chat.id, f"{text}")
else: else:
@@ -132,17 +130,9 @@ async def get_group_info(message: types.Message):
if group: if group:
await bot.send_message( await bot.send_message(
message.chat.id, message.chat.id,
f"""Группа: {group.title} f"Группа: {group.title}\nID группы: {group.chat_id}\n\nКоличество цитат: {len(group.quotes)}\n"
ID группы: {group.chat_id} f"Последние добавленные: /get_quotes\n\nТекущие настройки:\n Антиспам: {group.antispam}\n "
f"Ежедневные отчёты: {group.report}\n Праздники: {group.reminder}")
Количество цитат: {len(group.quotes)}
Последние добавленные: /get_quotes
Текущие настройки:
Антиспам: {group.antispam}
Ежедневные отчёты: {group.report}
Праздники: {group.reminder}""",
)
else: else:
await bot.send_message(message.chat.id, "Группа не включена. Включить: /enable_stan") await bot.send_message(message.chat.id, "Группа не включена. Включить: /enable_stan")
+26 -9
View File
@@ -6,7 +6,7 @@ from telebot import types
from .admin_commands import bot from .admin_commands import bot
from .constants import (LOG_COMM, FAQ, LIB, RULES, RUS, RUS_ENG_TABLE, ENG_RUS_TABLE, PYTHONCHATRU, ZEN, LUTZ_ID, from .constants import (LOG_COMM, FAQ, LIB, RULES, RUS, RUS_ENG_TABLE, ENG_RUS_TABLE, PYTHONCHATRU, ZEN, LUTZ_ID,
BDMTSS_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 .helpers import represent_as_get, detect_args, is_admin, fetch_rule
from .report import update_stats, increment from .report import update_stats, increment
from .stan import act, speak 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) 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 ] """ """ [ 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)) 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(): if len(args) > 1 and args[-1].isdigit():
index = int(args[-1]) index = int(args[-1])
await send_or_reply( await send_or_reply(message, f"<b>Правило {index}</b>\n<i>{fetch_rule(index)}</i>", reply_markup=markup)
message,
f"<b>Правило {args[-1]}</b>\n<i>{fetch_rule(index)}</i>",
reply_markup=markup,
)
else: else:
await send_or_reply(message, "Читай...", reply_markup=markup) await send_or_reply(message, "Читай...", reply_markup=markup)
await bot.delete_message(message.chat.id, message.id) await bot.delete_message(message.chat.id, message.id)
@@ -225,9 +231,7 @@ async def send_neprivet(message: types.Message):
message.text, message.text,
) )
) )
markup = types.InlineKeyboardMarkup() await _neprivet(message)
markup.add(types.InlineKeyboardButton("👋 Непривет", url="https://neprivet.com/"), row_width=1)
await send_or_reply(message, "Пожалуйста, не пишите просто «Привет» в чате.", reply_markup=markup)
await bot.delete_message(message.chat.id, message.id) 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) 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 ] """ """ [ COUNTER ] """
+2 -1
View File
@@ -35,6 +35,7 @@ ALLOWED_WORDS = [
"support", "support",
"jetbrains", "jetbrains",
] ]
HELLO_EXAMPLES = ('привет', 'привет всем', 'ку', 'здравствуйте', 'прив', 'всем прив', 'ребята всем привет', 'прива')
ONLY_RUS_LETTERS = "ёйцукенгшщзхъфывапролджэячсмитьбю" ONLY_RUS_LETTERS = "ёйцукенгшщзхъфывапролджэячсмитьбю"
ONLY_ENG_LETTERS = string.ascii_lowercase 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', '💸', 'P2P', 'Идет набор', 'в лс', 'нaбop', 'комaндy', 'kpuпт', 'бupж', '💰', 'пишитe', '💸',
'$ в неделю', 'обучаю с нуля', 'доходы для всех', 'по трейдингу', 'фальшивые рубли', '‼️', '$ в неделю', 'обучаю с нуля', 'доходы для всех', 'по трейдингу', 'фальшивые рубли', '‼️',
'зароботку', 'Набuраю', 'цuфр', 'Обучу вас', 'прuб', 'остались курсы', 'Crypt', 'kр', 'рu', 'зароботку', 'Набuраю', 'цuфр', 'Обучу вас', 'прuб', 'остались курсы', 'Crypt', 'kр', 'рu',
'в лc', "в личные сообщения"} 'в лc', "в личные сообще"}
NON_GRATA = {"дудар", "хауди", "dudar"} NON_GRATA = {"дудар", "хауди", "dudar"}
BAN_WORDS = {"GREEN"} BAN_WORDS = {"GREEN"}
+11 -2
View File
@@ -3,8 +3,8 @@ import logging.handlers
from telebot import types from telebot import types
from .config import WHITEIDS from .config import WHITEIDS
from .constants import LOG_COMM, ALLOWED_WORDS, URL_RX 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 from .helpers import is_spam, is_in_not_allowed, is_ban_words_in_caption, cleaned_text, remove_spaces
from .models import is_antispam_enabled 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: def is_white_id(message: types.Message) -> bool:
return message.from_user.id in WHITEIDS 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() == ''
+10 -3
View File
@@ -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(text).split():
for word in cleaned_text.split(): word = word.strip()
word = word.strip().lower()
if not word: if not word:
continue continue
if any(e in ONLY_RUS_LETTERS for e in word) and any(e in ONLY_ENG_LETTERS for e in word): 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 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: def is_in_not_allowed(word_list: list, msg: str) -> bool:
for word in word_list: for word in word_list:
if word in msg.casefold(): if word in msg.casefold():
+21 -6
View File
@@ -17,7 +17,7 @@ from src import helpers
# нужно не дать делать реальные запросы в тестах, конкретно тут -за Правилами чата # нужно не дать делать реальные запросы в тестах, конкретно тут -за Правилами чата
helpers.is_url_reachable = lambda a: True helpers.is_url_reachable = lambda a: True
from src.commands import bot 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 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) 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} params = {'text': text}
chat = types.Chat(id=11, type='group') 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: if reply_to:
params["reply_to_message"] = types.Message(message_id=2, from_user=user, date=None, chat=chat, 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="") 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="привет всем")]) await self.bot.process_new_updates([get_update('/neprivet', reply_to="привет всем")])
self.assertEqual(RESULTS[0][0]['text'], "Пожалуйста, не пишите просто «Привет» в чате.") self.assertEqual(RESULTS[0][0]['text'], "Пожалуйста, не пишите просто «Привет» в чате.")
self.assertTrue("https://neprivet.com/" in RESULTS[0][0]['reply_markup']) 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): async def test_lutz(self):
await self.bot.process_new_updates([get_update('/lutz', reply_to="привет всем")]) await self.bot.process_new_updates([get_update('/lutz', reply_to="привет всем")])
@@ -441,8 +450,8 @@ class TestBot(IsolatedAsyncioTestCase):
async def test_get_quotes(self): async def test_get_quotes(self):
params = ( params = (
("Цитаты отсутствуют. Подробнее: /get_group_info", []), ("Цитаты отсутствуют. Подробнее: /get_group_info", []),
("Количество цитат: 1\n\\Последние добавленные\n\n· one", ['one']), ("Количество цитат: 1\n\nПоследние добавленные:\n\n· one", ['one']),
("Количество цитат: 2\n\\Последние добавленные\n\n· one\n· two", ['one', 'two']), ("Количество цитат: 2\n\nПоследние добавленные:\n\n· one\n· two", ['one', 'two']),
) )
for expected, quotes in params: for expected, quotes in params:
with self.subTest(f"get_quotes {quotes}"): with self.subTest(f"get_quotes {quotes}"):
@@ -453,7 +462,7 @@ class TestBot(IsolatedAsyncioTestCase):
RESULTS.clear() RESULTS.clear()
async def test_get_group_info(self): 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" f"Текущие настройки:\n Антиспам: 1\n Ежедневные отчёты: 1\n Праздники: 1"
params = ( params = (
("Группа не включена. Включить: /enable_stan", None), ("Группа не включена. Включить: /enable_stan", None),
@@ -467,6 +476,12 @@ class TestBot(IsolatedAsyncioTestCase):
self.assertEqual(expected, RESULTS[0][0]['text']) self.assertEqual(expected, RESULTS[0][0]['text'])
RESULTS.clear() 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__': if __name__ == '__main__':
main() main()
+24 -3
View File
@@ -1,12 +1,14 @@
from unittest import TestCase, main from unittest import TestCase, main
from unittest.mock import MagicMock from unittest.mock import MagicMock
from src.constants import SPAM, BAN_WORDS, ADMIN_ID, RULES_TEXT 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 = ( CASES = (
"Оформим резидeнтствo ОАЭ без предoплаты за 5000 дирхам. Пoдробнoсти в лc", "Оформим резидeнтствo ОАЭ без предoплаты за 5000 дирхам. Пoдробнoсти в лc",
"Всем привет Я в поисках людей в kpиптo кoмaнду У нас вы пройдете 6ecплaтноe обученue 1-3 дня Можно совмещать с основной paбoтoй", "Всем привет Я в поисках людей в kpиптo кoмaнду У нас вы пройдете 6ecплaтноe обученue 1-3 дня Можно совмещать с основной paбoтoй",
"Здравствуйте! Нужны 2 человека от 200 $/день. Можно без опыта Пишите в личные сообщения +", "Здравствуйте! Нужны 2 человека от 200 $/день. Можно без опыта Пишите в личные сообщения +",
"Здравствуйте!Нужны 2 человека от 200 $/день.Пишите в личные сообщение +"
) )
@@ -44,7 +46,6 @@ class TestSpam(TestCase):
self.assertFalse(is_mixed("text='слово'")) self.assertFalse(is_mixed("text='слово'"))
class TestOthers(TestCase): class TestOthers(TestCase):
def test_ban(self): def test_ban(self):
for word in BAN_WORDS: for word in BAN_WORDS:
@@ -86,7 +87,7 @@ class TestOthers(TestCase):
self.assertEqual(is_admin(m), expected) self.assertEqual(is_admin(m), expected)
def test_fetch_rule(self): 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}"): with self.subTest(f"fetch rule {index}"):
self.assertEqual(fetch_rule(index), text) self.assertEqual(fetch_rule(index), text)
@@ -94,6 +95,26 @@ class TestOthers(TestCase):
self.assertEqual(fetch_rule(0), 'Пока не придумали') self.assertEqual(fetch_rule(0), 'Пока не придумали')
self.assertEqual(fetch_rule(100), 'Пока не придумали') 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__': if __name__ == '__main__':
main() main()