diff --git a/.gitignore b/.gitignore index e94c2fe..69bec5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -bot.log .atico/ -.env +bot.log +.env \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 8f1b069..8845cc4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,4 +11,4 @@ install: - pipenv install -d script: - pytest - - flake8 + - flake8 \ No newline at end of file diff --git a/README.md b/README.md index 532fecf..f83f01c 100644 --- a/README.md +++ b/README.md @@ -19,29 +19,39 @@ $ source ./bin/activate $ ./bin/pip install git+https://github.com/misanram/pydeckard.git@Instalar-desde-GitHub ~~~ -A developer needs to install a few more packages: +As a developer, you must install it in this other way: ~~~console $ ./bin/pip install git+https://github.com/misanram/pydeckard.git@Instalar-desde-GitHub[dev] ~~~ -Next step is to set your bot token for development: +After installation, the next step is to create the .env configuration file and +the file for automatic program startup. -~~~console -$ echo 'TELEGRAM_BOT_TOKEN = ""' > .env -~~~ +During the process, you will be asked to enter your Telegram token and +will be prompted with other configuration-related questions. The only required +item is the Telegram token. -Now you can launch the bot with: +To do this, activate the virtual environment and run: ~~~console -$ python3 bot.py +$ pydeckard --setup ~~~ -~~~systemd +You can now launch the bot, activating the virtual environment and running:: +~~~console +$ pydeckard +~~~ +.. or delegate the startup of the application to your operating system +using the instructions that setup has provided. +You can view the bot log using: +~~~console +$ journalctl -u pydeckard.service -f systemd +~~~ You can use the flag `--verbose` (or `-v') to get more information in the console: @@ -56,4 +66,4 @@ Use pytest: ~~~console $ python3 -m pytest -~~~ +~~~ \ No newline at end of file diff --git a/pydeckard/bot.py b/pydeckard/bot.py index 6607694..0898305 100644 --- a/pydeckard/bot.py +++ b/pydeckard/bot.py @@ -1,19 +1,20 @@ #!/usr/bin/enb python3 + from datetime import datetime as DateTime import itertools import argparse import logging import sys import time +from logging.handlers import RotatingFileHandler import telegram from telegram import Update from telegram.ext import ApplicationBuilder, filters, MessageHandler, CommandHandler, ContextTypes from telegram.constants import ParseMode - -from pydeckard import utils from pydeckard import config +from pydeckard import utils class DeckardBot(): @@ -27,25 +28,22 @@ def get_options(self): parser = argparse.ArgumentParser( prog='bot', description='PyDeckard Bot', - epilog='Text at the bottom of help', + epilog='', ) parser.add_argument('--setup', action='store_true', help='Start the setup wizard') args = parser.parse_args() if args.setup: utils.setup_bot() - def set_logger(self): - self.logger = logging.getLogger('bot') - + self.logger = logging.getLogger('pydeckard') console_handler = logging.StreamHandler() logging.basicConfig( - level=logging.DEBUG, # Pone el nivel de todos los logger a WARNING - format='%(asctime)s [%(name)s] %(levelname)s: %(message)s', - handlers=[console_handler], - force=True - ) - + level=logging.WARNING, # Pone el nivel de todos los logger a WARNING + format='%(asctime)s [%(name)s] %(levelname)s: %(message)s', + handlers=[console_handler], + force=True, + ) # Ajustamos el nivel del logger bot self.logger.setLevel(config.LOG_LEVEL) config.log(self.logger.info) @@ -53,7 +51,6 @@ def set_logger(self): def trace(self, msg): self.logger.info(msg) - async def command_status(self, update: Update, context: ContextTypes.DEFAULT_TYPE): self.trace('Received command: /status') python_version = sys.version.split(maxsplit=1)[0] @@ -136,8 +133,10 @@ async def welcome(self, update: Update, context): "-> It could be kindly removed 🗑" else: if utils.is_bot(new_member): - await context.bot.delete_message(update.message.chat_id, - update.message.message_id) + await context.bot.delete_message( + update.message.chat_id, + update.message.message_id, + ) if await context.bot.kick_chat_member(update.message.chat_id, new_member.id): msg = (f"*{new_member.username}* has been banned because I " "considered it was a bot. ") @@ -186,8 +185,13 @@ def run(self): def main(): + """ + Arranca el bot + """ + bot = DeckardBot() bot.run() + if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/pydeckard/config.py b/pydeckard/config.py index b15cd1c..d15d71c 100644 --- a/pydeckard/config.py +++ b/pydeckard/config.py @@ -103,7 +103,7 @@ def bot_replies_enabled() -> bool: ("elixir",): "BIBA ELICSÍR!! ¥", ("cobol",): "BIBA KOBOL!! 💾", ("fortran",): "BIBA FORRRTRÁN!! √", - (r"c\+\+",): "BIBA CEMASMÁS!! ⊕", + ("c++",): "BIBA CEMASMÁS!! ⊕", ("javascript",): "BIBA JABAESCRIP!! 🔮", ("php",): "BIBA PEACHEPÉ!.! ⛱", ("perl",): "BIBA PERRRRRL! 🐫", @@ -121,3 +121,7 @@ def bot_replies_enabled() -> bool: "tiempo... como lágrimas en la lluvia. Es hora de morir. 🔫", ("python", "pitón", "piton"): THE_ZEN_OF_PYTHON, } + +MAXLEN_FOR_USERNAME_TO_TREAT_AS_HUMAN = 100 + +CHINESE_CHARS_MAX_PERCENT = 0.15 \ No newline at end of file diff --git a/pydeckard/utils.py b/pydeckard/utils.py index 45a0b60..f2e32b0 100644 --- a/pydeckard/utils.py +++ b/pydeckard/utils.py @@ -45,25 +45,23 @@ def is_tgmember_sect(first_name: str): return "tgmember.com" in first_name.lower() -def is_bot(user: User): +def is_bot(user: User) -> bool: """ Returns True if a new user is a bot. So far only the length of the username is checked. In the future, we can add more conditions and use a score/weight of the probability of being a bot. :param user: The new User - :typus user: User + :type user: User :return: True if the new user is considered a bot (according to our rules) :rtype: bool """ # Add all the checks that you consider necessary - return any( - ( - not is_valid_name(user), - too_much_chinese_chars(user.first_name), - is_tgmember_sect(user.first_name), - ) - ) + return any([ + not is_valid_name(user), + too_much_chinese_chars(user.first_name), + is_tgmember_sect(user.first_name), + ]) @functools.lru_cache() @@ -210,22 +208,21 @@ def setup_bot(): root_path = Path(sys.prefix) bin_path = Path(sys.executable).parent - bot_executable = bin_path / 'bot' + bot_executable = bin_path / 'pydeckard' env_path = root_path / '.env' system_name = platform.system() print(f'\n--- Asistente de configuración para PyDeckard (SO: {system_name}) ---\n\n') - parameters = [('TELEGRAM_BOT_TOKEN', 'Introduzca el Token del Bot', None, str), - ('VERBOSITY', 'Nivel de verbosidad', (0.0, 1.0), float), - ('LOG_LEVEL', 'Nivel de registro de logs', ['DEBUG', 'INFO', 'WARNING', 'ERROR'], str), - ('POLL_INTERVAL', 'Intervalo de polling para la API de Telegram', (1, 10), int), - ('BOT_GREETING', 'Saludo del bot', None, str), - ('MAX_HUMAN_USERNAME_LENGTH', 'Longitud máxima del username', None, int), - ('MAX_CHINESE_CHARS_PERCENT', 'Máximo porcentaje de caracteres chinos en username', (0.0, - 1.0), float), - ('WELCOME_DELAY', 'Tiempo de retardo para la bienvenida (seg)', None, int), - ] + parameters = [ + ('TELEGRAM_BOT_TOKEN', 'Introduzca el Token del Bot', None, str), + ('LOG_LEVEL', 'Nivel de registro de logs', ['DEBUG', 'INFO', 'WARNING', 'ERROR'], str), + ('POLL_INTERVAL', 'Intervalo de polling para la API de Telegram', (1, 10), int), + ('BOT_GREETING', 'Saludo del bot', None, str), + ('MAX_HUMAN_USERNAME_LENGTH', 'Longitud máxima del username', None, int), + ('MAX_CHINESE_CHARS_PERCENT', 'Máximo porcentaje de caracteres chinos en username', (0.0, 1.0), float), + ('WELCOME_DELAY', 'Tiempo de retardo para la bienvenida (seg)', None, int), + ] try: items_env = {key: validate_input(*args) for key, *args in parameters} @@ -248,41 +245,47 @@ def setup_bot(): service_path = root_path / 'pydeckard.service' service_content = f"""[Unit] - Description=PyDeckard - After=network.target - - [Service] - Type=simple - User={user_name} - Group={group_name} - WorkingDirectory={root_path} - ExecStart={bot_executable} - Environment=PYTHONUNBUFFERED=1 - Restart=always - - [Install] - WantedBy=multi-user.target - Alias=PyDeckard.service - """ - +Description=PyDeckard +After=network.target + +[Service] +Type=simple +User={user_name} +Group={group_name} +WorkingDirectory={root_path} +ExecStart={bot_executable} +Environment=PYTHONUNBUFFERED=1 +Restart=always + +[Install] +WantedBy=multi-user.target +Alias=PyDeckard.service +""" with open(service_path, 'w') as f: f.write(service_content) print(f'\nArchivo pydeckard.service creado en {root_path}') + print(f'\nPara configurar, activar e iniciar el service en systemd ejecute los siguientes comandos:') + print(f'\nsudo cp {service_path} /etc/systemd/system/') print('sudo systemctl daemon-reload') print('sudo systemctl enable --now pydeckard') - sys.exit(0) elif system_name == 'Darwin': - print('Entorno macOS detectado, configuración realizada, pregúntele a Apple® como arrancarlo.') + print( + 'Entorno macOS detectado, configuración realizada, pregúntele' + ' a Apple® como arrancarlo.' + ) sys.exit(1) elif system_name == 'Windows': - print('Entorno Windows detectado, configuración realizada, pregúntele a Microsoft® como arrancarlo.') + print( + 'Entorno Windows detectado, configuración realizada, pregúntele + ' a Microsoft® como arrancarlo.' + ) sys.exit(1) elif system_name == 'Java': print('Entorno Jython detectado. Usted mismo.') - sys.exit(1) + sys.exit(1) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f058c9f..7b53341 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,10 +36,8 @@ include = ["pydeckard*"] where = ["."] [project.scripts] -bot = "pydeckard.bot:main" - +pydeckard = "pydeckard.bot:main" [tool.ruff] line-length = 120 -target-version = "py312" - +target-version = "py312" \ No newline at end of file diff --git a/test/test_utils.py b/test/test_utils.py index 86687b8..1fe3dfd 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -137,7 +137,7 @@ def test_tipo_int4(monkeypatch): assert validate_input('Dato', None, int) == 3 -def test_tipo_int5(monkeypatch): + def test_tipo_int5(monkeypatch): # Separador _ monkeypatch.setattr('builtins.input', lambda _: '1_000_000') @@ -157,13 +157,13 @@ def test_tipo_int7(monkeypatch): assert validate_input('Dato', None, int) == 10 -def test_tipo_int8(monkeypatch): + def test_tipo_int8(monkeypatch): # Hexadecimal con espacios monkeypatch.setattr('builtins.input', lambda _: ' 0xff ') assert validate_input('Dato', None, int) == 255 - + def test_tipo_int15(monkeypatch, capsys): # Float entradas = iter(['1.0', '1'])