Spam words moved to DB. Add command: /add_spam
This commit is contained in:
-110
@@ -1,110 +0,0 @@
|
|||||||
# A generic, single database configuration.
|
|
||||||
|
|
||||||
[alembic]
|
|
||||||
# path to migration scripts
|
|
||||||
script_location = alembic
|
|
||||||
|
|
||||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
|
||||||
# Uncomment the line below if you want the files to be prepended with date and time
|
|
||||||
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
|
||||||
# for all available tokens
|
|
||||||
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
|
||||||
|
|
||||||
# sys.path path, will be prepended to sys.path if present.
|
|
||||||
# defaults to the current working directory.
|
|
||||||
prepend_sys_path = .
|
|
||||||
|
|
||||||
# timezone to use when rendering the date within the migration file
|
|
||||||
# as well as the filename.
|
|
||||||
# If specified, requires the python-dateutil library that can be
|
|
||||||
# installed by adding `alembic[tz]` to the pip requirements
|
|
||||||
# string value is passed to dateutil.tz.gettz()
|
|
||||||
# leave blank for localtime
|
|
||||||
# timezone =
|
|
||||||
|
|
||||||
# max length of characters to apply to the
|
|
||||||
# "slug" field
|
|
||||||
# truncate_slug_length = 40
|
|
||||||
|
|
||||||
# set to 'true' to run the environment during
|
|
||||||
# the 'revision' command, regardless of autogenerate
|
|
||||||
# revision_environment = false
|
|
||||||
|
|
||||||
# set to 'true' to allow .pyc and .pyo files without
|
|
||||||
# a source .py file to be detected as revisions in the
|
|
||||||
# versions/ directory
|
|
||||||
# sourceless = false
|
|
||||||
|
|
||||||
# version location specification; This defaults
|
|
||||||
# to alembic/versions. When using multiple version
|
|
||||||
# directories, initial revisions must be specified with --version-path.
|
|
||||||
# The path separator used here should be the separator specified by "version_path_separator" below.
|
|
||||||
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
|
|
||||||
|
|
||||||
# version path separator; As mentioned above, this is the character used to split
|
|
||||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
|
||||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
|
||||||
# Valid values for version_path_separator are:
|
|
||||||
#
|
|
||||||
# version_path_separator = :
|
|
||||||
# version_path_separator = ;
|
|
||||||
# version_path_separator = space
|
|
||||||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
|
||||||
|
|
||||||
# set to 'true' to search source files recursively
|
|
||||||
# in each "version_locations" directory
|
|
||||||
# new in Alembic version 1.10
|
|
||||||
# recursive_version_locations = false
|
|
||||||
|
|
||||||
# the output encoding used when revision files
|
|
||||||
# are written from script.py.mako
|
|
||||||
# output_encoding = utf-8
|
|
||||||
|
|
||||||
sqlalchemy.url = sqlite:///E:\Projects\PythonChatRu\LutzPyBot\app\db\db.sqlite
|
|
||||||
|
|
||||||
|
|
||||||
[post_write_hooks]
|
|
||||||
# post_write_hooks defines scripts or Python functions that are run
|
|
||||||
# on newly generated revision scripts. See the documentation for further
|
|
||||||
# detail and examples
|
|
||||||
|
|
||||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
|
||||||
# hooks = black
|
|
||||||
# black.type = console_scripts
|
|
||||||
# black.entrypoint = black
|
|
||||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
[loggers]
|
|
||||||
keys = root,sqlalchemy,alembic
|
|
||||||
|
|
||||||
[handlers]
|
|
||||||
keys = console
|
|
||||||
|
|
||||||
[formatters]
|
|
||||||
keys = generic
|
|
||||||
|
|
||||||
[logger_root]
|
|
||||||
level = WARN
|
|
||||||
handlers = console
|
|
||||||
qualname =
|
|
||||||
|
|
||||||
[logger_sqlalchemy]
|
|
||||||
level = WARN
|
|
||||||
handlers =
|
|
||||||
qualname = sqlalchemy.engine
|
|
||||||
|
|
||||||
[logger_alembic]
|
|
||||||
level = INFO
|
|
||||||
handlers =
|
|
||||||
qualname = alembic
|
|
||||||
|
|
||||||
[handler_console]
|
|
||||||
class = StreamHandler
|
|
||||||
args = (sys.stderr,)
|
|
||||||
level = NOTSET
|
|
||||||
formatter = generic
|
|
||||||
|
|
||||||
[formatter_generic]
|
|
||||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
||||||
datefmt = %H:%M:%S
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Generic single-database configuration.
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
from logging.config import fileConfig
|
|
||||||
|
|
||||||
from sqlalchemy import engine_from_config
|
|
||||||
from sqlalchemy import pool
|
|
||||||
|
|
||||||
from alembic import context
|
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
|
||||||
# access to the values within the .ini file in use.
|
|
||||||
config = context.config
|
|
||||||
|
|
||||||
# Interpret the config file for Python logging.
|
|
||||||
# This line sets up loggers basically.
|
|
||||||
if config.config_file_name is not None:
|
|
||||||
fileConfig(config.config_file_name)
|
|
||||||
|
|
||||||
# add your model's MetaData object here
|
|
||||||
# for 'autogenerate' support
|
|
||||||
# from myapp import mymodel
|
|
||||||
# target_metadata = mymodel.Base.metadata
|
|
||||||
target_metadata = None
|
|
||||||
|
|
||||||
# other values from the config, defined by the needs of env.py,
|
|
||||||
# can be acquired:
|
|
||||||
# my_important_option = config.get_main_option("my_important_option")
|
|
||||||
# ... etc.
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_offline() -> None:
|
|
||||||
"""Run migrations in 'offline' mode.
|
|
||||||
|
|
||||||
This configures the context with just a URL
|
|
||||||
and not an Engine, though an Engine is acceptable
|
|
||||||
here as well. By skipping the Engine creation
|
|
||||||
we don't even need a DBAPI to be available.
|
|
||||||
|
|
||||||
Calls to context.execute() here emit the given string to the
|
|
||||||
script output.
|
|
||||||
|
|
||||||
"""
|
|
||||||
url = config.get_main_option("sqlalchemy.url")
|
|
||||||
context.configure(
|
|
||||||
url=url,
|
|
||||||
target_metadata=target_metadata,
|
|
||||||
literal_binds=True,
|
|
||||||
dialect_opts={"paramstyle": "named"},
|
|
||||||
)
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
|
||||||
context.run_migrations()
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_online() -> None:
|
|
||||||
"""Run migrations in 'online' mode.
|
|
||||||
|
|
||||||
In this scenario we need to create an Engine
|
|
||||||
and associate a connection with the context.
|
|
||||||
|
|
||||||
"""
|
|
||||||
connectable = engine_from_config(
|
|
||||||
config.get_section(config.config_ini_section, {}),
|
|
||||||
prefix="sqlalchemy.",
|
|
||||||
poolclass=pool.NullPool,
|
|
||||||
)
|
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
|
||||||
context.configure(
|
|
||||||
connection=connection, target_metadata=target_metadata
|
|
||||||
)
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
|
||||||
context.run_migrations()
|
|
||||||
|
|
||||||
|
|
||||||
if context.is_offline_mode():
|
|
||||||
run_migrations_offline()
|
|
||||||
else:
|
|
||||||
run_migrations_online()
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
"""${message}
|
|
||||||
|
|
||||||
Revision ID: ${up_revision}
|
|
||||||
Revises: ${down_revision | comma,n}
|
|
||||||
Create Date: ${create_date}
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
${imports if imports else ""}
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = ${repr(up_revision)}
|
|
||||||
down_revision = ${repr(down_revision)}
|
|
||||||
branch_labels = ${repr(branch_labels)}
|
|
||||||
depends_on = ${repr(depends_on)}
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
${upgrades if upgrades else "pass"}
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
${downgrades if downgrades else "pass"}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
"""add permissions
|
|
||||||
|
|
||||||
Revision ID: 1b1f99b95b38
|
|
||||||
Revises: 9d160884887c
|
|
||||||
Create Date: 2023-07-30 18:33:35.400071
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '1b1f99b95b38'
|
|
||||||
down_revision = '9d160884887c'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
pass
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
"""My migration message
|
|
||||||
|
|
||||||
Revision ID: 9d160884887c
|
|
||||||
Revises:
|
|
||||||
Create Date: 2023-04-06 00:47:17.551695
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '9d160884887c'
|
|
||||||
down_revision = None
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
pass
|
|
||||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 212 KiB |
+1
-1
@@ -1 +1 @@
|
|||||||
from . import set_commands
|
# from . import set_commands # Временное решение. Не коммитить раскомментированным - упадут тесты.
|
||||||
|
|||||||
@@ -49,23 +49,6 @@ RULES = types.InlineKeyboardButton("👨🏼🎓 Правила чата", ur
|
|||||||
FAQ = types.InlineKeyboardButton("❔ Частые вопросы", url="https://telegra.ph/faq-10-07-4")
|
FAQ = types.InlineKeyboardButton("❔ Частые вопросы", url="https://telegra.ph/faq-10-07-4")
|
||||||
LIB = types.InlineKeyboardButton("📚 Книги", url="https://telegra.ph/what-to-read-10-06")
|
LIB = types.InlineKeyboardButton("📚 Книги", url="https://telegra.ph/what-to-read-10-06")
|
||||||
|
|
||||||
SPAM = {"me.sv/", "tg.sv/", "goo.by/", "go.sv/", "intim.video/", "uclck.ru/",
|
|
||||||
'💎', '🔞', '➡️', '💰', '💸', '‼️',
|
|
||||||
'раб0т', '0плат', 'новый способ заработка', 'криптовалют', 'цифровых валют', 'Binance', 'ByBit',
|
|
||||||
'Помогу заработать', 'арбитраж', 'DEX бирж', 'любую девушку', 'прибыль у вaс', 'обучaю быстро',
|
|
||||||
'Предлагаю новый вид заработка', 'межбиржев', 'КУПЮРЫ', 'в неделю выходит до', 'soax',
|
|
||||||
'предложить заработок', 'голые фот', 'интимные фот', 'заработка в крипте', 'Веду набор', 'Идёт набор',
|
|
||||||
'Доход от', 'Trading', 'P2P', 'Идет набор', '$ в неделю', 'обучаю с нуля', 'доходы для всех',
|
|
||||||
'по трейдингу', 'фальшивые рубли', 'зароботку', 'Обучу вас', 'остались курсы', 'Crypt',
|
|
||||||
'Ищу партнеров', 'ищем партнеров', 'в поиске партнеров', 'обучим бесплатно', 'обучу бесплатно',
|
|
||||||
'обучим бесплатно', 'обучаю бесплатно', 'цифровых активов', 'цифровые активы', 'цифровые валюты',
|
|
||||||
'никаких предоплат', 'предоплаты не требуется', 'без предоплат', 'предоплата не требуется',
|
|
||||||
'предоплата не нужна', 'можете ознакомиться', 'хочу пригласить Вас', 'сотрудничество на удаленке',
|
|
||||||
'Только совершеннолетние', 'хороший заработок',
|
|
||||||
|
|
||||||
'в лc', "в личные сообще", 'напиши в личку', 'Писать в ЛИЧКУ', 'Пишите в личку', 'в л.с.', 'в лс',
|
|
||||||
'Писать в личные', 'в личку', 'в личке', 'в личных сооб', 'пишите в личные', 'пишем в личные', 'Плюс в личные'
|
|
||||||
}
|
|
||||||
|
|
||||||
NON_GRATA = {"дудар", "хауди", "dudar"}
|
NON_GRATA = {"дудар", "хауди", "dudar"}
|
||||||
BAN_WORDS = {"GREEN"}
|
BAN_WORDS = {"GREEN"}
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,4 @@
|
|||||||
""" Send User's info. """
|
""" Send User's info. """
|
||||||
|
|
||||||
from urllib import parse
|
from urllib import parse
|
||||||
from urllib.error import URLError
|
from urllib.error import URLError
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
@@ -7,8 +6,9 @@ from urllib.request import urlopen
|
|||||||
from telebot import types
|
from telebot import types
|
||||||
|
|
||||||
from .constants import (
|
from .constants import (
|
||||||
SPAM, BAN_WORDS, NON_GRATA, ADMIN_ID, ONLY_RUS_LETTERS, ONLY_ENG_LETTERS, RULES_TEXT, SYMBOLS, URL_RX
|
NON_GRATA, ADMIN_ID, ONLY_RUS_LETTERS, ONLY_ENG_LETTERS, RULES_TEXT, SYMBOLS, BAN_WORDS
|
||||||
)
|
)
|
||||||
|
from .models import session, BadWord
|
||||||
|
|
||||||
WARNS: dict[int, int] = {}
|
WARNS: dict[int, int] = {}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ def is_spam(message_text: str) -> bool:
|
|||||||
"""
|
"""
|
||||||
if is_mixed(message_text):
|
if is_mixed(message_text):
|
||||||
return True
|
return True
|
||||||
return any(text.casefold() in message_text.casefold() for text in SPAM)
|
return any(bad_word.word.casefold() in message_text.casefold() for bad_word in session.query(BadWord).all())
|
||||||
|
|
||||||
|
|
||||||
def is_mixed(text: str) -> bool:
|
def is_mixed(text: str) -> bool:
|
||||||
|
|||||||
@@ -54,6 +54,15 @@ class Chat(Base):
|
|||||||
return f'Chat({self.chat_id=}, {self.title=})'
|
return f'Chat({self.chat_id=}, {self.title=})'
|
||||||
|
|
||||||
|
|
||||||
|
class BadWord(Base):
|
||||||
|
__tablename__ = 'bad_words'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
word = Column(String)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'BadWord({self.id}: {self.word=})'
|
||||||
|
|
||||||
|
|
||||||
CACHE: dict[int, Type[Chat]] = {}
|
CACHE: dict[int, Type[Chat]] = {}
|
||||||
|
|
||||||
|
|
||||||
@@ -95,6 +104,12 @@ def add_quote(chat_id: int, text: str):
|
|||||||
_reload_chat(chat_id)
|
_reload_chat(chat_id)
|
||||||
|
|
||||||
|
|
||||||
|
def add_spam(chat_id: int, text: str):
|
||||||
|
session.add(BadWord(word=text))
|
||||||
|
session.commit()
|
||||||
|
_reload_chat(chat_id)
|
||||||
|
|
||||||
|
|
||||||
def chat_by_id(chat_id: int) -> None | Chat:
|
def chat_by_id(chat_id: int) -> None | Chat:
|
||||||
return CACHE.get(chat_id)
|
return CACHE.get(chat_id)
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ for white_id in WHITEIDS:
|
|||||||
telebot.types.BotCommand("links", "/ссылки на правила, чзв и библиотеку"),
|
telebot.types.BotCommand("links", "/ссылки на правила, чзв и библиотеку"),
|
||||||
telebot.types.BotCommand("tsya", "/тся и /ться"),
|
telebot.types.BotCommand("tsya", "/тся и /ться"),
|
||||||
telebot.types.BotCommand("add", "добавить цитату [Whitelist]"),
|
telebot.types.BotCommand("add", "добавить цитату [Whitelist]"),
|
||||||
|
telebot.types.BotCommand("add_spam", "добавить в спам [Whitelist]"),
|
||||||
telebot.types.BotCommand("remove", "удалить цитату [Whitelist]"),
|
telebot.types.BotCommand("remove", "удалить цитату [Whitelist]"),
|
||||||
],
|
],
|
||||||
scope=telebot.types.BotCommandScopeChatMember(PYTHONCHATRU, white_id)
|
scope=telebot.types.BotCommandScopeChatMember(PYTHONCHATRU, white_id)
|
||||||
|
|||||||
+18
-1
@@ -9,7 +9,8 @@ from telebot import types, util
|
|||||||
from .constants import LOG_COMM
|
from .constants import LOG_COMM
|
||||||
from .filters import is_white_id
|
from .filters import is_white_id
|
||||||
from .helpers import is_nongrata, short_user_data, has_links
|
from .helpers import is_nongrata, short_user_data, has_links
|
||||||
from .models import all_chat_quotes, add_quote, is_quote_in_chat, delete_quote_in_chat, CACHE
|
from .models import all_chat_quotes, add_quote, is_quote_in_chat, delete_quote_in_chat, CACHE, add_spam, session, \
|
||||||
|
BadWord
|
||||||
from .robot import bot
|
from .robot import bot
|
||||||
|
|
||||||
TYPING_TIMEOUT = 0.13 / 4 # Reading time is quarter of the same text writing time
|
TYPING_TIMEOUT = 0.13 / 4 # Reading time is quarter of the same text writing time
|
||||||
@@ -72,6 +73,22 @@ async def add_stan_quote(message: types.Message):
|
|||||||
await bot.send_message(message.chat.id, f"⛔️ Не добавил, есть токое: {quote}", parse_mode='Markdown')
|
await bot.send_message(message.chat.id, f"⛔️ Не добавил, есть токое: {quote}", parse_mode='Markdown')
|
||||||
|
|
||||||
|
|
||||||
|
@bot.message_handler(func=is_white_id, commands=["add_spam"])
|
||||||
|
async def add_spam_handler(message: types.Message):
|
||||||
|
if message.reply_to_message and message.reply_to_message.text:
|
||||||
|
quote = (message.quote and message.quote.text) or message.reply_to_message.text
|
||||||
|
logging.error(f'{session.query(BadWord).filter_by(word=quote).first()}')
|
||||||
|
if not session.query(BadWord).filter_by(word=quote).first():
|
||||||
|
add_spam(message.chat.id, quote)
|
||||||
|
ok_message = await bot.send_message(message.chat.id, f"⭐️ Добавил в спам: {quote}", parse_mode='Markdown')
|
||||||
|
await bot.delete_message(message.chat.id, message.id)
|
||||||
|
await asyncio.sleep(3)
|
||||||
|
await bot.delete_message(message.chat.id, ok_message.id)
|
||||||
|
logging.info(LOG_COMM % (message.chat.title, message.from_user.id, message.from_user.full_name, f'[SPAM] {message.reply_to_message.text}'))
|
||||||
|
else:
|
||||||
|
await bot.send_message(message.chat.id, f"⛔️ Не добавил, есть токое: {quote}", parse_mode='Markdown')
|
||||||
|
|
||||||
|
|
||||||
@bot.message_handler(func=is_white_id, commands=["remove"])
|
@bot.message_handler(func=is_white_id, commands=["remove"])
|
||||||
async def remove_stan_quote(message: types.Message):
|
async def remove_stan_quote(message: types.Message):
|
||||||
if message.reply_to_message and message.reply_to_message.text:
|
if message.reply_to_message and message.reply_to_message.text:
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
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 BAN_WORDS, ADMIN_ID, RULES_TEXT
|
||||||
from src.helpers import (
|
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,
|
is_spam, is_mixed, is_ban_words_in_caption, is_in_not_allowed, is_nongrata, is_admin, fetch_rule, cleaned_text,
|
||||||
remove_spaces, has_no_letters, has_links
|
remove_spaces, has_no_letters, has_links
|
||||||
)
|
)
|
||||||
|
from src.models import BadWord, session
|
||||||
|
|
||||||
REAL_CASES = (
|
REAL_CASES = (
|
||||||
"Оформим резидeнтствo ОАЭ без предoплаты за 5000 дирхам. Пoдробнoсти в лc",
|
"Оформим резидeнтствo ОАЭ без предoплаты за 5000 дирхам. Пoдробнoсти в лc",
|
||||||
@@ -48,9 +49,9 @@ TEST_CASES = ('here it me.sv/122', "My site is tg.sv/home", "look here goo.by/ho
|
|||||||
|
|
||||||
class TestSpam(TestCase):
|
class TestSpam(TestCase):
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
for spam_word in SPAM:
|
for spam_word in session.query(BadWord).all():
|
||||||
with self.subTest(f"check spam word({spam_word})"):
|
with self.subTest(f"check spam word({spam_word.word})"):
|
||||||
self.assertTrue(is_spam(spam_word))
|
self.assertTrue(is_spam(spam_word.word))
|
||||||
|
|
||||||
def test_case_first(self):
|
def test_case_first(self):
|
||||||
for text in REAL_CASES:
|
for text in REAL_CASES:
|
||||||
|
|||||||
Reference in New Issue
Block a user