From b2967a6147b76a5d854dd25505ff92792d2e8b52 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 01:06:05 +0100 Subject: [PATCH 01/28] Commit de prueba para crear paquete instalable desde GitHub --- .env.example | 0 fabfile.py | 11 ----------- pydeckard.service => pydeckard.service.example | 0 pydeckard/__init__.py | 0 bot.py => pydeckard/bot.py | 0 config.py => pydeckard/config.py | 0 utils.py => pydeckard/utils.py | 0 run.sh | 6 ------ 8 files changed, 17 deletions(-) create mode 100644 .env.example delete mode 100644 fabfile.py rename pydeckard.service => pydeckard.service.example (100%) create mode 100644 pydeckard/__init__.py rename bot.py => pydeckard/bot.py (100%) rename config.py => pydeckard/config.py (100%) rename utils.py => pydeckard/utils.py (100%) delete mode 100755 run.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e69de29 diff --git a/fabfile.py b/fabfile.py deleted file mode 100644 index 92c8966..0000000 --- a/fabfile.py +++ /dev/null @@ -1,11 +0,0 @@ -from fabric.api import env, local, cd, run - -env.hosts = ["pythoncanarias.es"] - - -def deploy(): - local("git push") - with cd("~/pydeckard"): - run("git pull") - run("pipenv install") - run("supervisorctl restart pydeckard") diff --git a/pydeckard.service b/pydeckard.service.example similarity index 100% rename from pydeckard.service rename to pydeckard.service.example diff --git a/pydeckard/__init__.py b/pydeckard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot.py b/pydeckard/bot.py similarity index 100% rename from bot.py rename to pydeckard/bot.py diff --git a/config.py b/pydeckard/config.py similarity index 100% rename from config.py rename to pydeckard/config.py diff --git a/utils.py b/pydeckard/utils.py similarity index 100% rename from utils.py rename to pydeckard/utils.py diff --git a/run.sh b/run.sh deleted file mode 100755 index 144be68..0000000 --- a/run.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -# Master script. - -cd "$(dirname "$0")" -source ~/.pyenv/versions/3.12.4/envs/pydeckard/bin/activate -exec python bot.py From 733e654a5c424fd7e0aa5564b0e382b35eedcbeb Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 01:13:02 +0100 Subject: [PATCH 02/28] Commit de prueba para crear paquete instalable desde GitHub --- .env.example | 8 +++++++ .gitignore | 12 ++--------- .travis.yml | 7 ++++-- README.md | 9 ++++---- pydeckard.service.example | 11 ++++++++-- pydeckard/bot.py | 6 ++++-- pyproject.toml | 44 ++++++++++++++++++++++++++++++++++++++ test/test_bot_detection.py | 2 +- test/test_replies.py | 3 +-- test/test_utils.py | 3 +-- 10 files changed, 80 insertions(+), 25 deletions(-) diff --git a/.env.example b/.env.example index e69de29..59c8e7a 100644 --- a/.env.example +++ b/.env.example @@ -0,0 +1,8 @@ +TELEGRAM_BOT_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +VERBOSITY=0.33 +LOG_LEVEL=WARNING +POLL_INTERVAL=3 +BOT_GREETING="Hi! I'm a friendly, crazy slightly psychopath robot" +MAX_HUMAN_USERNAME_LENGTH=100 +MAX_CHINESE_CHARS_PERCENT=0.15 +WELCOME_DELAY=330 diff --git a/.gitignore b/.gitignore index 88aec55..e94c2fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,3 @@ -*.pyc -pycache -myconfig.py -virtualenv -.idea -.vscode -.DS_Store -.env -.pytest_cache -.python-version bot.log +.atico/ +.env diff --git a/.travis.yml b/.travis.yml index 964d029..8f1b069 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,11 @@ dist: bionic language: python python: - - 3.6 - - 3.7 +python: + - "3.9" + - "3.10" + - "3.11" + - "3.12" install: - pip install pipenv - pipenv install -d diff --git a/README.md b/README.md index b5dc7da..718436a 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,13 @@ Telegram Bot made in Python to automate different tasks of [Python Canarias](htt ## Installation Create a virtualenv for Python3 and install dependencies. In this -example we are using pyenv: +example we are using python -m venv: ~~~console -$ pyenv virtualenv 3.12.4 pydeckard -$ pyenv activate pydeckard -$ pip install -r requirements.txt +$ python -m venv pydeckard +$ cd pydeckard +$ 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: diff --git a/pydeckard.service.example b/pydeckard.service.example index 50a6d5c..c64515a 100644 --- a/pydeckard.service.example +++ b/pydeckard.service.example @@ -1,10 +1,17 @@ [Unit] Description=PyDeckard +After=network.target [Service] +User=el_que_ejecute_el_bot +Group=el_que_ejecute_el_bot Restart=always -WorkingDirectory=/home/jileon/pydeckard/ -ExecStart=/home/jileon/.pyenv/versions/3.12.4/envs/pydeckard/bin/python bot.py +WorkingDirectory=/ruta_al_entorno_virtual +ExecStart=/ruta_al_entorno_virtual/bin/bot +# La onfiguración del bot puede hacerse: + A) Colocando el archivo .env en el WorkingDirectory + B) Colocando el archivo .env en cualquier directorio que User pueda leer y declarandolo en + EnvironmentFile=/cualquier_directorio/.env [Install] WantedBy=multi-user.target diff --git a/pydeckard/bot.py b/pydeckard/bot.py index 4f6a433..4c5d1e4 100644 --- a/pydeckard/bot.py +++ b/pydeckard/bot.py @@ -187,7 +187,9 @@ def run(self): self.trace('Bot is ready') application.run_polling(poll_interval=config.POLL_INTERVAL) - -if __name__ == "__main__": +def main(): bot = DeckardBot() bot.run() + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index ecba4b1..e7b6ba0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,47 @@ +[build-system] +requires = ['setuptools>=61.0'] +build-backend = 'setuptools.build_meta' + +[project] +name = 'pydeckard' +description = 'Un bot de Telegram para el canal de Python Canarias' +requires-python = '>=3.12' +authors = [ + { name = 'Python Canarias', email = 'info@pythoncanarias.es' }, +] + +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Intended Audience :: Developers", + "Topic :: Communications :: Chat", +] + +[project.urls] +Homepage = "https://github.com/pythoncanarias/pydeckard.git" + +[project] +license = "GPL-3.0-or-later" +license-files = ["LICENSE"] +dynamic = ['dependencies', 'readme'] + +[tool.setuptools.dynamic] +readme = { file = ['README.md', ] } +dependencies = { file = 'requirements.txt' } + +[tool.setuptools.dynamic.optional-dependencies] +dev = { file = ["dev-requirements.txt"] } + +[tool.setuptools.packages.find] +include = ["pydeckard*"] +where = ["."] + +[project.scripts] +bot = "pydeckard.bot:main" + + [tool.ruff] line-length = 120 target-version = "py312" diff --git a/test/test_bot_detection.py b/test/test_bot_detection.py index b74cd12..51f35c0 100644 --- a/test/test_bot_detection.py +++ b/test/test_bot_detection.py @@ -1,6 +1,6 @@ from unittest.mock import Mock import pytest -import utils +from pydeckard import utils # testing is_chinese diff --git a/test/test_replies.py b/test/test_replies.py index e893a7a..2868b3d 100644 --- a/test/test_replies.py +++ b/test/test_replies.py @@ -1,7 +1,6 @@ import pytest -import config -import utils +from pydeckard import config, utils @pytest.fixture() diff --git a/test/test_utils.py b/test/test_utils.py index 54d7450..fc115d6 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,12 +1,11 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import datetime import pytest from freezegun import freeze_time -import utils +from pydeckard import utils @freeze_time("2019-05-16 13:35:16") From 8c3e90b9814bdb5b7add1b6f3d71f1d338e92f4d Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 01:17:57 +0100 Subject: [PATCH 03/28] =?UTF-8?q?Correcci=C3=B3n=20en=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 718436a..26c86a4 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ $ ./bin/pip install git+https://github.com/misanram/pydeckard.git@Instalar-desde A developer needs to install a few more packages: ~~~console -$ pip install -r dev-requirements.txt +$ ./bin/pip install git+https://github.com/misanram/pydeckard.git@Instalar-desde-GitHub[dev] ~~~ Next step is to set your bot token for development: From 2a796c825a3433ee9d814788def3b7b527984eb5 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 01:20:24 +0100 Subject: [PATCH 04/28] =?UTF-8?q?Correcci=C3=B3n=20en=20pyproject.toml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e7b6ba0..27817e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,6 @@ requires-python = '>=3.12' authors = [ { name = 'Python Canarias', email = 'info@pythoncanarias.es' }, ] - classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.12", @@ -18,15 +17,13 @@ classifiers = [ "Intended Audience :: Developers", "Topic :: Communications :: Chat", ] - -[project.urls] -Homepage = "https://github.com/pythoncanarias/pydeckard.git" - -[project] license = "GPL-3.0-or-later" license-files = ["LICENSE"] dynamic = ['dependencies', 'readme'] +[project.urls] +Homepage = "https://github.com/pythoncanarias/pydeckard.git" + [tool.setuptools.dynamic] readme = { file = ['README.md', ] } dependencies = { file = 'requirements.txt' } From cc0e6577d7907d05dc60aea1a80df91a6f663bb8 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 01:22:19 +0100 Subject: [PATCH 05/28] =?UTF-8?q?Correcci=C3=B3n=20en=20pyproject.toml=20s?= =?UTF-8?q?e=20a=C3=B1ade=20version.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 27817e5..c033030 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ classifiers = [ license = "GPL-3.0-or-later" license-files = ["LICENSE"] dynamic = ['dependencies', 'readme'] +version="0.1.0" [project.urls] Homepage = "https://github.com/pythoncanarias/pydeckard.git" From 1ece59e391bf7a0104a3aea0da89f06e673a8344 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 01:23:59 +0100 Subject: [PATCH 06/28] =?UTF-8?q?Correcci=C3=B3n=20en=20pyproject.toml=20s?= =?UTF-8?q?e=20corrige=20license.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c033030..66c7d05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ authors = [ classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.12", - "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Intended Audience :: Developers", "Topic :: Communications :: Chat", From 471e080c1d0545065058c0bf846412ef90f87af1 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 01:24:56 +0100 Subject: [PATCH 07/28] =?UTF-8?q?Correcci=C3=B3n=20en=20pyproject.toml=20s?= =?UTF-8?q?e=20corrige=20requires-python.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 66c7d05..460615e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = 'setuptools.build_meta' [project] name = 'pydeckard' description = 'Un bot de Telegram para el canal de Python Canarias' -requires-python = '>=3.12' +requires-python = '>=3.10' authors = [ { name = 'Python Canarias', email = 'info@pythoncanarias.es' }, ] From 842eec405528e8863bb17eda5478fb2e12ebbd4a Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 01:31:56 +0100 Subject: [PATCH 08/28] =?UTF-8?q?Correcci=C3=B3n=20en=20bot=20de=20importa?= =?UTF-8?q?ciones=20internas=20del=20paquete.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/bot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pydeckard/bot.py b/pydeckard/bot.py index 4c5d1e4..5dd54be 100644 --- a/pydeckard/bot.py +++ b/pydeckard/bot.py @@ -13,8 +13,9 @@ from telegram.ext import ApplicationBuilder, filters, MessageHandler, CommandHandler, ContextTypes from telegram.constants import ParseMode -import config -import utils + +from pydeckard import utils +from pydeckard import config class DeckardBot(): From f1401d953e2e32c57c7bef7edc78ac57c7843b35 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 01:32:17 +0100 Subject: [PATCH 09/28] =?UTF-8?q?Correcci=C3=B3n=20en=20bot=20de=20importa?= =?UTF-8?q?ciones=20internas=20del=20paquete.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 460615e..a79348a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ classifiers = [ license = "GPL-3.0-or-later" license-files = ["LICENSE"] dynamic = ['dependencies', 'readme'] -version="0.1.0" +version="0.1.1" [project.urls] Homepage = "https://github.com/pythoncanarias/pydeckard.git" From 43f2245ba86720022f7e6cbf75202c0fbab43800 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 01:33:48 +0100 Subject: [PATCH 10/28] =?UTF-8?q?Correcci=C3=B3n=20en=20utils=20de=20impor?= =?UTF-8?q?taciones=20internas=20del=20paquete.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/utils.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pydeckard/utils.py b/pydeckard/utils.py index d0c8e4f..84e80ca 100644 --- a/pydeckard/utils.py +++ b/pydeckard/utils.py @@ -5,7 +5,7 @@ from typing import Tuple, Optional, NamedTuple from telegram import User -import config +from pydeckard import config def is_chinese(c): diff --git a/pyproject.toml b/pyproject.toml index a79348a..2b3f204 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ classifiers = [ license = "GPL-3.0-or-later" license-files = ["LICENSE"] dynamic = ['dependencies', 'readme'] -version="0.1.1" +version="0.1.2" [project.urls] Homepage = "https://github.com/pythoncanarias/pydeckard.git" From 4ed54fb4537e56877f7a8be778498c237ad3c4e9 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 02:57:32 +0100 Subject: [PATCH 11/28] =?UTF-8?q?Creaci=C3=B3n=20del=20parametro=20setup?= =?UTF-8?q?=20para=20configurar=20el=20bot.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 ++++++++---- pydeckard/bot.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 +- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 26c86a4..532fecf 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,19 @@ $ echo 'TELEGRAM_BOT_TOKEN = ""' > .env Now you can launch the bot with: ~~~console -$ python bot.py +$ python3 bot.py ~~~ -You can use the flag `--verbose` (or `-v') to get more information in rhe console: +~~~systemd + + + + + +You can use the flag `--verbose` (or `-v') to get more information in the console: ~~~console -$ python bot.py --verbose +$ python3 bot.py --verbose ~~~ @@ -49,5 +55,5 @@ $ python bot.py --verbose Use pytest: ~~~console -$ python -m pytest +$ python3 -m pytest ~~~ diff --git a/pydeckard/bot.py b/pydeckard/bot.py index 5dd54be..4775b1a 100644 --- a/pydeckard/bot.py +++ b/pydeckard/bot.py @@ -1,5 +1,5 @@ #!/usr/bin/enb python3 - +import os from datetime import datetime as DateTime import itertools import argparse @@ -7,6 +7,7 @@ import sys import time from logging.handlers import RotatingFileHandler +from pathlib import Path import telegram from telegram import Update @@ -33,8 +34,11 @@ def get_options(self): epilog='Text at the bottom of help', ) parser.add_argument('-v', '--verbose', action='store_true') + parser.add_argument('--setup', action='store_true', help='Start the setup wizard') args = parser.parse_args() self.verbose = args.verbose + if args.setup: + self.setup() def set_logger(self): self.logger = logging.getLogger('bot') @@ -58,6 +62,9 @@ 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] @@ -188,6 +195,56 @@ def run(self): self.trace('Bot is ready') application.run_polling(poll_interval=config.POLL_INTERVAL) +def setup(self): + """ + Arranca un asistente para la configuración del bot + """ + + root_path = Path(sys.prefix) + bin_path = Path(sys.executable).parent + bot_executable = bin_path / 'bot' + + # Archivos destino + env_path = root_path / '.env' + service_path = root_path / 'pydeckard.service' + + print(f'--- Configuración Automática PyDeckard (Raíz: {root_path}) ---') + + token = input('Introduzca el Token del Bot: ') + + with open(env_path, 'w') as f: + f.write(f'TELEGRAM_BOT_TOKEN={token}\n') + print(f"✅ Archivo .env creado en {root_path}") + + user_name = os.getlogin() + service_content = f"""[Unit] + Description=PyDeckard + After=network.target + + [Service] + Type=simple + User={user_name} + WorkingDirectory={root_path} + ExecStart={bot_executable} + Restart=always + + [Install] + WantedBy=multi-user.target + Alias=PyDeckard.serviceget + """ + + with open(service_path, 'w') as f: + f.write(service_content) + + print(f'✅ Archivo pydeckard.service creado en {root_path}') + print('\nA continuación debe copiar el archivo pydeckard.service a /etc/systemd/system/, activiar el ' + 'servicio y ejecutarlo') + print(f'1. sudo cp {service_path} /etc/systemd/system/') + print('2. sudo systemctl daemon-reload') + print('3. sudo systemctl enable --now pydeckard') + + sys.exit(0) + def main(): bot = DeckardBot() bot.run() diff --git a/pyproject.toml b/pyproject.toml index 2b3f204..2c5f9d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ classifiers = [ license = "GPL-3.0-or-later" license-files = ["LICENSE"] dynamic = ['dependencies', 'readme'] -version="0.1.2" +version="0.1.4" [project.urls] Homepage = "https://github.com/pythoncanarias/pydeckard.git" From 9e1a38579efda9d5fdfac901def382871a0b7d78 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 03:00:29 +0100 Subject: [PATCH 12/28] =?UTF-8?q?Correcci=C3=B3n=20de=20errores=20en=20bot?= =?UTF-8?q?.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/bot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydeckard/bot.py b/pydeckard/bot.py index 4775b1a..8b4cf06 100644 --- a/pydeckard/bot.py +++ b/pydeckard/bot.py @@ -38,7 +38,7 @@ def get_options(self): args = parser.parse_args() self.verbose = args.verbose if args.setup: - self.setup() + setup_bot() def set_logger(self): self.logger = logging.getLogger('bot') @@ -195,7 +195,7 @@ def run(self): self.trace('Bot is ready') application.run_polling(poll_interval=config.POLL_INTERVAL) -def setup(self): +def setup_bot(): """ Arranca un asistente para la configuración del bot """ From c06c47c9a573964509c52b4aec431de7cbbedc88 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 03:01:05 +0100 Subject: [PATCH 13/28] =?UTF-8?q?Correcci=C3=B3n=20de=20errores=20en=20bot?= =?UTF-8?q?.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2c5f9d2..daeb792 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ classifiers = [ license = "GPL-3.0-or-later" license-files = ["LICENSE"] dynamic = ['dependencies', 'readme'] -version="0.1.4" +version="0.1.5" [project.urls] Homepage = "https://github.com/pythoncanarias/pydeckard.git" From cb7463e576a18122c24ada505ce0d9cfe1123c3b Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 03:04:21 +0100 Subject: [PATCH 14/28] =?UTF-8?q?Correcci=C3=B3n=20de=20errores=20en=20bot?= =?UTF-8?q?.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/bot.py | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pydeckard/bot.py b/pydeckard/bot.py index 8b4cf06..4b76327 100644 --- a/pydeckard/bot.py +++ b/pydeckard/bot.py @@ -230,18 +230,18 @@ def setup_bot(): [Install] WantedBy=multi-user.target - Alias=PyDeckard.serviceget + Alias=PyDeckard.service """ with open(service_path, 'w') as f: f.write(service_content) print(f'✅ Archivo pydeckard.service creado en {root_path}') - print('\nA continuación debe copiar el archivo pydeckard.service a /etc/systemd/system/, activiar el ' + print('\nA continuación debe copiar el archivo pydeckard.service a /etc/systemd/system/, activar el ' 'servicio y ejecutarlo') - print(f'1. sudo cp {service_path} /etc/systemd/system/') - print('2. sudo systemctl daemon-reload') - print('3. sudo systemctl enable --now pydeckard') + print(f'sudo cp {service_path} /etc/systemd/system/') + print('sudo systemctl daemon-reload') + print('sudo systemctl enable --now pydeckard') sys.exit(0) diff --git a/pyproject.toml b/pyproject.toml index daeb792..e3a17af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ classifiers = [ license = "GPL-3.0-or-later" license-files = ["LICENSE"] dynamic = ['dependencies', 'readme'] -version="0.1.5" +version="0.1.6" [project.urls] Homepage = "https://github.com/pythoncanarias/pydeckard.git" From 4edd5cc933ad18c2d7ebbac54057b7dbac390f27 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 10:27:30 +0100 Subject: [PATCH 15/28] =?UTF-8?q?Moviendo=20el=20asistente=20de=20configur?= =?UTF-8?q?aci=C3=B3n=20a=20utils.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/bot.py | 67 ++-------------------------- pydeckard/utils.py | 106 +++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 111 insertions(+), 64 deletions(-) diff --git a/pydeckard/bot.py b/pydeckard/bot.py index 4b76327..ee169c7 100644 --- a/pydeckard/bot.py +++ b/pydeckard/bot.py @@ -1,13 +1,10 @@ #!/usr/bin/enb python3 -import os from datetime import datetime as DateTime import itertools import argparse import logging import sys import time -from logging.handlers import RotatingFileHandler -from pathlib import Path import telegram from telegram import Update @@ -33,25 +30,20 @@ def get_options(self): description='PyDeckard Bot', epilog='Text at the bottom of help', ) - parser.add_argument('-v', '--verbose', action='store_true') parser.add_argument('--setup', action='store_true', help='Start the setup wizard') args = parser.parse_args() self.verbose = args.verbose if args.setup: - setup_bot() + utils.setup_bot() def set_logger(self): self.logger = logging.getLogger('bot') - file_handler = RotatingFileHandler('bot.log', maxBytes=1_000_000, backupCount=5) - console_handler = logging.NullHandler() - if self.verbose: - console_handler = logging.StreamHandler() - + console_handler = logging.StreamHandler() logging.basicConfig( - level=logging.WARNING, # Pone el nivel de todos los logger a WARNING + level=logging.DEBUG, # Pone el nivel de todos los logger a WARNING format='%(asctime)s [%(name)s] %(levelname)s: %(message)s', - handlers=[file_handler,console_handler], + handlers=[console_handler], force=True ) @@ -63,8 +55,6 @@ 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] @@ -195,55 +185,6 @@ def run(self): self.trace('Bot is ready') application.run_polling(poll_interval=config.POLL_INTERVAL) -def setup_bot(): - """ - Arranca un asistente para la configuración del bot - """ - - root_path = Path(sys.prefix) - bin_path = Path(sys.executable).parent - bot_executable = bin_path / 'bot' - - # Archivos destino - env_path = root_path / '.env' - service_path = root_path / 'pydeckard.service' - - print(f'--- Configuración Automática PyDeckard (Raíz: {root_path}) ---') - - token = input('Introduzca el Token del Bot: ') - - with open(env_path, 'w') as f: - f.write(f'TELEGRAM_BOT_TOKEN={token}\n') - print(f"✅ Archivo .env creado en {root_path}") - - user_name = os.getlogin() - service_content = f"""[Unit] - Description=PyDeckard - After=network.target - - [Service] - Type=simple - User={user_name} - WorkingDirectory={root_path} - ExecStart={bot_executable} - Restart=always - - [Install] - WantedBy=multi-user.target - Alias=PyDeckard.service - """ - - with open(service_path, 'w') as f: - f.write(service_content) - - print(f'✅ Archivo pydeckard.service creado en {root_path}') - print('\nA continuación debe copiar el archivo pydeckard.service a /etc/systemd/system/, activar el ' - 'servicio y ejecutarlo') - print(f'sudo cp {service_path} /etc/systemd/system/') - print('sudo systemctl daemon-reload') - print('sudo systemctl enable --now pydeckard') - - sys.exit(0) def main(): bot = DeckardBot() diff --git a/pydeckard/utils.py b/pydeckard/utils.py index 84e80ca..2898667 100644 --- a/pydeckard/utils.py +++ b/pydeckard/utils.py @@ -1,7 +1,12 @@ import functools import datetime +import grp +import platform +import pwd import random import re +import sys +from pathlib import Path from typing import Tuple, Optional, NamedTuple from telegram import User @@ -126,3 +131,104 @@ def since(reference) -> str: seconds %= 60 buff.append(f"{seconds} {pluralise(seconds, 'second')}") return " ".join(buff) + +def setup_bot(): + """ + Arranca un asistente para la configuración del bot y la creación de un sistema de arranque automáquico + por el sistema operativo + """ + + root_path = Path(sys.prefix) + bin_path = Path(sys.executable).parent + bot_executable = bin_path / 'bot' + + env_path = root_path / '.env' + + system_name = platform.system() + print(f"--- Asistente de configuración para PyDeckard (SO: {system_name}) ---") + + token = input('Introduzca el Token del Bot: ') + welcome_delay = input('Introduzca el retardo para la bienvenida: ') + chinese_chars = input('Porcentaje de caracteres chinos en username (0.0-1.0): ') + username_length = input('Longitud máxima del username: ') + greeting = input('Saludo del bot: ') + poll_interval = input('Intervalo de polling para la API de Telegram: ') + log_level = input('Nivel de registro de logs: ') + verbosity = input('Nivel de verbosidad: ') + + with open(env_path, 'w') as f: + f.write(f'VERBOSITY={verbosity}\n') + f.write(f'LOG_LEVEL={log_level}\n') + f.write(f'POLL_INTERVAL={poll_interval}\n') + f.write(f'BOT_GREETING ={greeting}\n') + f.write(f'MAX_HUMAN_USERNAME_LENGTH={username_length}\n') + f.write(f'CHINESE_CHARS={chinese_chars}\n') + f.write(f'WELCOME_DELAY={welcome_delay}\n') + + + + + + MAXLEN_FOR_USERNAME_TO_TREAT_AS_HUMAN = 100 + CHINESE_CHARS_MAX_PERCENT = 0.15 + + + + + + + + + + + + print(f"✅ Archivo .env creado en {root_path}") + + if system_name == "Linux": + setup_linux(root_path, bot_executable) + + + + stat_info = root_path.stat() + + user_name = pwd.getpwuid(stat_info.st_uid).pw_name + group_name = grp.getgrgid(stat_info.st_gid).gr_name + + # Archivos destino + + 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 + """ + + with open(service_path, 'w') as f: + f.write(service_content) + + print(f'✅ Archivo pydeckard.service creado en {root_path}') + print('\nA continuación debe copiar el archivo pydeckard.service a /etc/systemd/system/, activar el ' + 'servicio y ejecutarlo') + print(f'sudo cp {service_path} /etc/systemd/system/') + print('sudo systemctl daemon-reload') + print('sudo systemctl enable --now pydeckard') + + sys.exit(0) diff --git a/pyproject.toml b/pyproject.toml index e3a17af..e599b3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ classifiers = [ license = "GPL-3.0-or-later" license-files = ["LICENSE"] dynamic = ['dependencies', 'readme'] -version="0.1.6" +version="0.1.7" [project.urls] Homepage = "https://github.com/pythoncanarias/pydeckard.git" From 70b8ae836851410f94fb2db4a06a43e6f2966521 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 10:30:35 +0100 Subject: [PATCH 16/28] Eliminando el argumento verbose de bot.py --- pydeckard/bot.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pydeckard/bot.py b/pydeckard/bot.py index ee169c7..69baec6 100644 --- a/pydeckard/bot.py +++ b/pydeckard/bot.py @@ -21,7 +21,6 @@ class DeckardBot(): def __init__(self): self.get_options() self.set_logger() - self.verbose = False self.started_at = DateTime.now() def get_options(self): @@ -32,7 +31,6 @@ def get_options(self): ) parser.add_argument('--setup', action='store_true', help='Start the setup wizard') args = parser.parse_args() - self.verbose = args.verbose if args.setup: utils.setup_bot() From 5afe4752e2f2f75533c0a20f62768c95dac2058a Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 10:53:42 +0100 Subject: [PATCH 17/28] =?UTF-8?q?Adaptar=20la=20funci=C3=B3n=20setup=5Fbot?= =?UTF-8?q?=20a=20diferentes=20plataformas.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/utils.py | 112 ++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 62 deletions(-) diff --git a/pydeckard/utils.py b/pydeckard/utils.py index 2898667..61ee741 100644 --- a/pydeckard/utils.py +++ b/pydeckard/utils.py @@ -145,7 +145,7 @@ def setup_bot(): env_path = root_path / '.env' system_name = platform.system() - print(f"--- Asistente de configuración para PyDeckard (SO: {system_name}) ---") + print(f'--- Asistente de configuración para PyDeckard (SO: {system_name}) ---') token = input('Introduzca el Token del Bot: ') welcome_delay = input('Introduzca el retardo para la bienvenida: ') @@ -157,6 +157,7 @@ def setup_bot(): verbosity = input('Nivel de verbosidad: ') with open(env_path, 'w') as f: + f.write(f'TELEGRAM_BOT_TOKEN={token}\n') f.write(f'VERBOSITY={verbosity}\n') f.write(f'LOG_LEVEL={log_level}\n') f.write(f'POLL_INTERVAL={poll_interval}\n') @@ -165,70 +166,57 @@ def setup_bot(): f.write(f'CHINESE_CHARS={chinese_chars}\n') f.write(f'WELCOME_DELAY={welcome_delay}\n') - - - - MAXLEN_FOR_USERNAME_TO_TREAT_AS_HUMAN = 100 CHINESE_CHARS_MAX_PERCENT = 0.15 - - - - - - - - - - print(f"✅ Archivo .env creado en {root_path}") if system_name == "Linux": - setup_linux(root_path, bot_executable) - - - - stat_info = root_path.stat() - - user_name = pwd.getpwuid(stat_info.st_uid).pw_name - group_name = grp.getgrgid(stat_info.st_gid).gr_name - - # Archivos destino - - 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 - """ - - with open(service_path, 'w') as f: - f.write(service_content) - - print(f'✅ Archivo pydeckard.service creado en {root_path}') - print('\nA continuación debe copiar el archivo pydeckard.service a /etc/systemd/system/, activar el ' - 'servicio y ejecutarlo') - print(f'sudo cp {service_path} /etc/systemd/system/') - print('sudo systemctl daemon-reload') - print('sudo systemctl enable --now pydeckard') - - sys.exit(0) + stat_info = root_path.stat() + + user_name = pwd.getpwuid(stat_info.st_uid).pw_name + group_name = grp.getgrgid(stat_info.st_gid).gr_name + + 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 + """ + + with open(service_path, 'w') as f: + f.write(service_content) + + print(f'✅ Archivo pydeckard.service creado en {root_path}') + print('\nA continuación debe copiar el archivo pydeckard.service a /etc/systemd/system/, activar el ' + 'servicio y ejecutarlo') + print(f'sudo 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, lo tiene configurado, pregúntele a Apple® como arrancarlo.') + sys.exit(1) + + elif system_name == 'Windows': + print('ℹ️ Entorno Windows detectado, lo tiene configurado, pregúntele a Microsoft® como arrancarlo.') + sys.exit(1) + + elif system_name == 'Java': + print('ℹ️ Entorno Jython detectado. Hágaselo mirar.') + sys.exit(1) From 64b73077d88d030b25da9fc207989dc7358196ca Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 11:21:11 +0100 Subject: [PATCH 18/28] Manejar los unput de los items de .env como un diccionario para simplificar su escritura. --- pydeckard/config.py | 4 ---- pydeckard/utils.py | 35 ++++++++++++++--------------------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/pydeckard/config.py b/pydeckard/config.py index e8f3ced..b717ccf 100644 --- a/pydeckard/config.py +++ b/pydeckard/config.py @@ -121,7 +121,3 @@ 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 diff --git a/pydeckard/utils.py b/pydeckard/utils.py index 61ee741..a40fdcd 100644 --- a/pydeckard/utils.py +++ b/pydeckard/utils.py @@ -145,29 +145,22 @@ def setup_bot(): env_path = root_path / '.env' system_name = platform.system() + print(f'--- Asistente de configuración para PyDeckard (SO: {system_name}) ---') - token = input('Introduzca el Token del Bot: ') - welcome_delay = input('Introduzca el retardo para la bienvenida: ') - chinese_chars = input('Porcentaje de caracteres chinos en username (0.0-1.0): ') - username_length = input('Longitud máxima del username: ') - greeting = input('Saludo del bot: ') - poll_interval = input('Intervalo de polling para la API de Telegram: ') - log_level = input('Nivel de registro de logs: ') - verbosity = input('Nivel de verbosidad: ') - - with open(env_path, 'w') as f: - f.write(f'TELEGRAM_BOT_TOKEN={token}\n') - f.write(f'VERBOSITY={verbosity}\n') - f.write(f'LOG_LEVEL={log_level}\n') - f.write(f'POLL_INTERVAL={poll_interval}\n') - f.write(f'BOT_GREETING ={greeting}\n') - f.write(f'MAX_HUMAN_USERNAME_LENGTH={username_length}\n') - f.write(f'CHINESE_CHARS={chinese_chars}\n') - f.write(f'WELCOME_DELAY={welcome_delay}\n') - - MAXLEN_FOR_USERNAME_TO_TREAT_AS_HUMAN = 100 - CHINESE_CHARS_MAX_PERCENT = 0.15 + items_env = {"TELEGRAM_BOT_TOKEN": input("Introduzca el Token del Bot: "), + "VERBOSITY": input("Nivel de verbosidad: "), + "LOG_LEVEL": input("Nivel de registro de logs: "), + 'POLL_INTERVAL': input('Intervalo de polling para la API de Telegram: '), + 'BOT_GREETING': input('Saludo del bot: '), + 'MAX_HUMAN_USERNAME_LENGTH': input('Longitud máxima del username: '), + 'CHINESE_CHARS': input('Porcentaje de caracteres chinos en username (0.0-1.0): '), + 'WELCOME_DELAY': input('Introduzca el retardo para la bienvenida: '), + } + + with open(env_path, 'w') as fout: + lines = [f'{key}={value}\n' for key, value in items_env.items() if value.strip()] + fout.writelines(lines) print(f"✅ Archivo .env creado en {root_path}") From e95ee676173e69639e6369584f768e499208ae63 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 11:27:08 +0100 Subject: [PATCH 19/28] =?UTF-8?q?Cambio=20en=20la=20funci=C3=B3n=20config?= =?UTF-8?q?=20de=20config.py=20para=20que=20acepte=20items=20vac=C3=ADos.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydeckard/config.py b/pydeckard/config.py index b717ccf..a1d5a72 100644 --- a/pydeckard/config.py +++ b/pydeckard/config.py @@ -18,7 +18,7 @@ def log(self, logger_method, indent=False): def config(item, cast=lambda v: v, suppress_log=False, **kwargs): - value = _config(item, cast, **kwargs) + value = _config(item.strip(), cast, **kwargs) global _config_registry _config_registry.append(_ConfigItem(item, value, suppress_log)) return value From b072429d4d97d5e4ef70279a013597dd0622e1ce Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 11:28:51 +0100 Subject: [PATCH 20/28] =?UTF-8?q?Cambio=20en=20la=20funci=C3=B3n=20config?= =?UTF-8?q?=20de=20config.py=20para=20que=20acepte=20items=20vac=C3=ADos.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/config.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pydeckard/config.py b/pydeckard/config.py index a1d5a72..dedce07 100644 --- a/pydeckard/config.py +++ b/pydeckard/config.py @@ -18,10 +18,11 @@ def log(self, logger_method, indent=False): def config(item, cast=lambda v: v, suppress_log=False, **kwargs): - value = _config(item.strip(), cast, **kwargs) - global _config_registry - _config_registry.append(_ConfigItem(item, value, suppress_log)) - return value + if item: + value = _config(item, cast, **kwargs) + global _config_registry + _config_registry.append(_ConfigItem(item, value, suppress_log)) + return value def log(logger_method): From c8dede9abf03f909285664bcd3dba8a69321f038 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 11:30:32 +0100 Subject: [PATCH 21/28] =?UTF-8?q?Cambio=20en=20la=20funci=C3=B3n=20config?= =?UTF-8?q?=20de=20config.py=20para=20que=20acepte=20items=20vac=C3=ADos.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydeckard/config.py b/pydeckard/config.py index dedce07..15069e7 100644 --- a/pydeckard/config.py +++ b/pydeckard/config.py @@ -18,6 +18,7 @@ def log(self, logger_method, indent=False): def config(item, cast=lambda v: v, suppress_log=False, **kwargs): + print('*',item,'*') if item: value = _config(item, cast, **kwargs) global _config_registry From 37cddddf32b733cf33e748a36e0b15d4b24a26a0 Mon Sep 17 00:00:00 2001 From: misanram Date: Fri, 17 Apr 2026 11:50:38 +0100 Subject: [PATCH 22/28] =?UTF-8?q?Cambio=20en=20la=20funci=C3=B3n=20config?= =?UTF-8?q?=20de=20config.py=20para=20que=20acepte=20items=20vac=C3=ADos.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pydeckard/config.py b/pydeckard/config.py index 15069e7..df597b7 100644 --- a/pydeckard/config.py +++ b/pydeckard/config.py @@ -18,12 +18,12 @@ def log(self, logger_method, indent=False): def config(item, cast=lambda v: v, suppress_log=False, **kwargs): - print('*',item,'*') - if item: - value = _config(item, cast, **kwargs) - global _config_registry - _config_registry.append(_ConfigItem(item, value, suppress_log)) - return value + print (item) + print(kwargs) + value = _config(item, cast, **kwargs) + global _config_registry + _config_registry.append(_ConfigItem(item, value, suppress_log)) + return value def log(logger_method): From 336bd3a5e4ea8d863e28d42ebc956bd4373d6f01 Mon Sep 17 00:00:00 2001 From: misanram Date: Sat, 18 Apr 2026 12:55:45 +0100 Subject: [PATCH 23/28] =?UTF-8?q?Primera=20versi=C3=B3n=20de=20la=20funci?= =?UTF-8?q?=C3=B3n=20=5Finput?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/bot.py | 4 +++ pydeckard/config.py | 4 +-- pydeckard/utils.py | 79 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/pydeckard/bot.py b/pydeckard/bot.py index 69baec6..b815d1c 100644 --- a/pydeckard/bot.py +++ b/pydeckard/bot.py @@ -19,11 +19,14 @@ class DeckardBot(): def __init__(self): + print(1) self.get_options() + 1 / 0 self.set_logger() self.started_at = DateTime.now() def get_options(self): + print(2) parser = argparse.ArgumentParser( prog='bot', description='PyDeckard Bot', @@ -34,6 +37,7 @@ def get_options(self): if args.setup: utils.setup_bot() + def set_logger(self): self.logger = logging.getLogger('bot') diff --git a/pydeckard/config.py b/pydeckard/config.py index df597b7..b15cd1c 100644 --- a/pydeckard/config.py +++ b/pydeckard/config.py @@ -18,8 +18,6 @@ def log(self, logger_method, indent=False): def config(item, cast=lambda v: v, suppress_log=False, **kwargs): - print (item) - print(kwargs) value = _config(item, cast, **kwargs) global _config_registry _config_registry.append(_ConfigItem(item, value, suppress_log)) @@ -105,7 +103,7 @@ def bot_replies_enabled() -> bool: ("elixir",): "BIBA ELICSÍR!! ¥", ("cobol",): "BIBA KOBOL!! 💾", ("fortran",): "BIBA FORRRTRÁN!! √", - ("c\+\+",): "BIBA CEMASMÁS!! ⊕", + (r"c\+\+",): "BIBA CEMASMÁS!! ⊕", ("javascript",): "BIBA JABAESCRIP!! 🔮", ("php",): "BIBA PEACHEPÉ!.! ⛱", ("perl",): "BIBA PERRRRRL! 🐫", diff --git a/pydeckard/utils.py b/pydeckard/utils.py index a40fdcd..46077bc 100644 --- a/pydeckard/utils.py +++ b/pydeckard/utils.py @@ -52,7 +52,7 @@ def is_bot(user: User): score/weight of the probability of being a bot. :param user: The new User - :type user: User + :typus user: User :return: True if the new user is considered a bot (according to our rules) :rtype: bool """ @@ -132,10 +132,63 @@ def since(reference) -> str: buff.append(f"{seconds} {pluralise(seconds, 'second')}") return " ".join(buff) + +def _input(prompt_head, acceptable=None, typus=None): + """ Esta función realiza un input, para lo que crea un prompt, hace el input y comprueba que el valor + recibido sea aceptable en función de los parámetros que se le han pasado. + + + """ + + prompt_tail = '' + + if isinstance(acceptable, list): + prompt_tail = f" ({'/'.join(map(str, acceptable))})" + elif isinstance(acceptable, tuple): + prompt_tail = f" ({acceptable[0]}-{acceptable[1]})" + + prompt = f'{prompt_head}{prompt_tail}: ' + + while True: + try: + data = input(prompt).strip() + except KeyboardInterrupt: + print("\nCancelado") + break + if not (data and typus): + return None + + try: + data = typus(data) + except ValueError: + print(f'❌ Error: El valor debe ser de tipo {typus.__name__}') + continue + + if isinstance(acceptable, list) and data not in acceptable: + print(f'❌ Error: Debe ser una de estas opciones: {acceptable}') + continue + + if isinstance(acceptable, tuple): + if not (acceptable[0] <= data <= acceptable[1]): + print(f'❌ Error: Debe estar entre {acceptable[0]} y {acceptable[1]}.') + continue + + return str(data) + + def setup_bot(): """ - Arranca un asistente para la configuración del bot y la creación de un sistema de arranque automáquico - por el sistema operativo + Arranca un asistente para la configuración del bot y la creación de un sistema de arranque automático + en funcíon del sistema operativo del usuario. + + Realiza un input por cada parámetro de configuración necesario. + La lista parameters contiene todos los parámetros para los que hay que hacer un input, definidos cada + uno como una tupla de 4 elementos: + nombre del parámetro, + prompt para el input, + una tupla con dos valores para indicar un rango de valores admitidos OR una lista con valores para + indicar las distintas opciones admitidas OR None, + una calse para indicar el tipo de valor admitido """ root_path = Path(sys.prefix) @@ -148,15 +201,17 @@ def setup_bot(): print(f'--- Asistente de configuración para PyDeckard (SO: {system_name}) ---') - items_env = {"TELEGRAM_BOT_TOKEN": input("Introduzca el Token del Bot: "), - "VERBOSITY": input("Nivel de verbosidad: "), - "LOG_LEVEL": input("Nivel de registro de logs: "), - 'POLL_INTERVAL': input('Intervalo de polling para la API de Telegram: '), - 'BOT_GREETING': input('Saludo del bot: '), - 'MAX_HUMAN_USERNAME_LENGTH': input('Longitud máxima del username: '), - 'CHINESE_CHARS': input('Porcentaje de caracteres chinos en username (0.0-1.0): '), - 'WELCOME_DELAY': input('Introduzca el retardo para la bienvenida: '), - } + 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), + ('CHINESE_CHARS', 'Porcentaje de caracteres chinos en username', (0.0, 1.0), float), + ('CHINESE_CHARS', 'Tiempo de retardo para la bienvenida (seg)', None, int), + ] + + items_env = {key: _input(*args) for key, *args in parameters} with open(env_path, 'w') as fout: lines = [f'{key}={value}\n' for key, value in items_env.items() if value.strip()] From 069ee33d0b8a2799f31b9503a61f3a78b2cc0408 Mon Sep 17 00:00:00 2001 From: misanram Date: Sat, 18 Apr 2026 13:11:19 +0100 Subject: [PATCH 24/28] =?UTF-8?q?Segunda=20versi=C3=B3n=20de=20la=20funci?= =?UTF-8?q?=C3=B3n=20=5Finput?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/utils.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pydeckard/utils.py b/pydeckard/utils.py index 46077bc..3cf79c4 100644 --- a/pydeckard/utils.py +++ b/pydeckard/utils.py @@ -153,8 +153,7 @@ def _input(prompt_head, acceptable=None, typus=None): try: data = input(prompt).strip() except KeyboardInterrupt: - print("\nCancelado") - break + raise if not (data and typus): return None @@ -211,7 +210,11 @@ def setup_bot(): ('CHINESE_CHARS', 'Tiempo de retardo para la bienvenida (seg)', None, int), ] - items_env = {key: _input(*args) for key, *args in parameters} + try: + items_env = {key: _input(*args) for key, *args in parameters} + except KeyboardInterrupt: + pass + with open(env_path, 'w') as fout: lines = [f'{key}={value}\n' for key, value in items_env.items() if value.strip()] From 353f0163a8f352578e46c9fd667641135e4ac7b2 Mon Sep 17 00:00:00 2001 From: misanram Date: Sat, 18 Apr 2026 16:18:52 +0100 Subject: [PATCH 25/28] =?UTF-8?q?Tercera=20versi=C3=B3n=20de=20la=20funci?= =?UTF-8?q?=C3=B3n=20=5Finput?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/utils.py | 53 +++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/pydeckard/utils.py b/pydeckard/utils.py index 3cf79c4..dca9bfe 100644 --- a/pydeckard/utils.py +++ b/pydeckard/utils.py @@ -134,10 +134,15 @@ def since(reference) -> str: def _input(prompt_head, acceptable=None, typus=None): - """ Esta función realiza un input, para lo que crea un prompt, hace el input y comprueba que el valor - recibido sea aceptable en función de los parámetros que se le han pasado. - - + """ Esta función está pensada para capturar los aprámetros que se van a usar para configurar el bot. + Realiza un input y valida eldato obtenido. + Pasos: + Crea una cadena de texto para usarla como proompt. + Pide el dato (input) + Valida el dato recibido + Devulve el dato validado o None + + Se puede interrumpir la captura de parámetros con Ctrl+C """ prompt_tail = '' @@ -160,42 +165,38 @@ def _input(prompt_head, acceptable=None, typus=None): try: data = typus(data) except ValueError: - print(f'❌ Error: El valor debe ser de tipo {typus.__name__}') + print(f'Error: El valor debe ser de tipo {typus.__name__}') continue if isinstance(acceptable, list) and data not in acceptable: - print(f'❌ Error: Debe ser una de estas opciones: {acceptable}') + print(f'Error: El valor debe ser una de estas opciones: {acceptable}') continue if isinstance(acceptable, tuple): if not (acceptable[0] <= data <= acceptable[1]): - print(f'❌ Error: Debe estar entre {acceptable[0]} y {acceptable[1]}.') + print(f'Error: El valor debe estar entre {acceptable[0]} y {acceptable[1]}.') continue return str(data) def setup_bot(): - """ - Arranca un asistente para la configuración del bot y la creación de un sistema de arranque automático - en funcíon del sistema operativo del usuario. + """ Arranca un asistente para la configuración del bot y la creación de un sistema de arranque automático + en funcíon del sistema operativo. Realiza un input por cada parámetro de configuración necesario. - La lista parameters contiene todos los parámetros para los que hay que hacer un input, definidos cada - uno como una tupla de 4 elementos: + La lista "parameters" contiene todos los parámetros definidos cada uno como una tupla de 4 elementos: nombre del parámetro, prompt para el input, una tupla con dos valores para indicar un rango de valores admitidos OR una lista con valores para indicar las distintas opciones admitidas OR None, - una calse para indicar el tipo de valor admitido + una clase para hacer el cast con tipo de valor admitido """ root_path = Path(sys.prefix) bin_path = Path(sys.executable).parent bot_executable = bin_path / 'bot' - env_path = root_path / '.env' - system_name = platform.system() print(f'--- Asistente de configuración para PyDeckard (SO: {system_name}) ---') @@ -213,16 +214,16 @@ def setup_bot(): try: items_env = {key: _input(*args) for key, *args in parameters} except KeyboardInterrupt: - pass - + print('Asistente cancelado por el usuario.') + sys.exit(1) with open(env_path, 'w') as fout: - lines = [f'{key}={value}\n' for key, value in items_env.items() if value.strip()] + lines = [f'{key}={value}\n' for key, value in items_env.items() if value] fout.writelines(lines) - print(f"✅ Archivo .env creado en {root_path}") + print(f'\n\nArchivo .env creado en {root_path}') - if system_name == "Linux": + if system_name == 'Linux': stat_info = root_path.stat() user_name = pwd.getpwuid(stat_info.st_uid).pw_name @@ -251,23 +252,21 @@ def setup_bot(): with open(service_path, 'w') as f: f.write(service_content) - print(f'✅ Archivo pydeckard.service creado en {root_path}') - print('\nA continuación debe copiar el archivo pydeckard.service a /etc/systemd/system/, activar el ' - 'servicio y ejecutarlo') - print(f'sudo cp {service_path} /etc/systemd/system/') + print(f'\nArchivo pydeckard.service creado en {root_path}') + 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, lo tiene configurado, 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, lo tiene configurado, 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. Hágaselo mirar.') + print('Entorno Jython detectado. Usted mismo.') sys.exit(1) From d3bdadca27d55b3b99cfd52455365a23093d28bc Mon Sep 17 00:00:00 2001 From: misanram Date: Sat, 18 Apr 2026 16:29:54 +0100 Subject: [PATCH 26/28] =?UTF-8?q?Cuarta=20versi=C3=B3n=20de=20la=20funci?= =?UTF-8?q?=C3=B3n=20=5Finput?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/bot.py | 3 --- pydeckard/utils.py | 35 ++++++++++++++++------------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/pydeckard/bot.py b/pydeckard/bot.py index b815d1c..6607694 100644 --- a/pydeckard/bot.py +++ b/pydeckard/bot.py @@ -19,14 +19,11 @@ class DeckardBot(): def __init__(self): - print(1) self.get_options() - 1 / 0 self.set_logger() self.started_at = DateTime.now() def get_options(self): - print(2) parser = argparse.ArgumentParser( prog='bot', description='PyDeckard Bot', diff --git a/pydeckard/utils.py b/pydeckard/utils.py index dca9bfe..906bf84 100644 --- a/pydeckard/utils.py +++ b/pydeckard/utils.py @@ -134,15 +134,14 @@ def since(reference) -> str: def _input(prompt_head, acceptable=None, typus=None): - """ Esta función está pensada para capturar los aprámetros que se van a usar para configurar el bot. - Realiza un input y valida eldato obtenido. - Pasos: - Crea una cadena de texto para usarla como proompt. - Pide el dato (input) - Valida el dato recibido - Devulve el dato validado o None - - Se puede interrumpir la captura de parámetros con Ctrl+C + """ This function is designed to capture the parameters that will be used to configure the bot. + It captures input and validates the data obtained. + Steps: + Create a text string to use as a prompt. + Request the input. + Validate the received data. + Return the validated data or None. + Parameter capture can be interrupted with Ctrl+C """ prompt_tail = '' @@ -181,16 +180,14 @@ def _input(prompt_head, acceptable=None, typus=None): def setup_bot(): - """ Arranca un asistente para la configuración del bot y la creación de un sistema de arranque automático - en funcíon del sistema operativo. - - Realiza un input por cada parámetro de configuración necesario. - La lista "parameters" contiene todos los parámetros definidos cada uno como una tupla de 4 elementos: - nombre del parámetro, - prompt para el input, - una tupla con dos valores para indicar un rango de valores admitidos OR una lista con valores para - indicar las distintas opciones admitidas OR None, - una clase para hacer el cast con tipo de valor admitido + """ A wizard starts to configure the bot and create an automatic startup system based on the operating system. + It performs an input for each required configuration parameter. + The "parameters" list contains all the defined parameters, each as a tuple of four elements: + parameter name, + prompt for input, + a tuple with two values to indicate a range of allowed values OR a list with values to indicate the + different allowed options OR None, + a class to cast the value to the allowed type """ root_path = Path(sys.prefix) From 10a98883cfb1b3eccd497fcbd1e9547f56feca06 Mon Sep 17 00:00:00 2001 From: misanram Date: Sat, 18 Apr 2026 21:31:03 +0100 Subject: [PATCH 27/28] =?UTF-8?q?Quinta=20versi=C3=B3n=20de=20la=20funci?= =?UTF-8?q?=C3=B3n=20=5Finput=20ahora=20se=20llama=20validate=5Finput=20Se?= =?UTF-8?q?=20a=C3=B1aden=20test=20para=20la=20funci=C3=B3n.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydeckard/utils.py | 49 +++-- test/test_utils.py | 451 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 476 insertions(+), 24 deletions(-) diff --git a/pydeckard/utils.py b/pydeckard/utils.py index 906bf84..45a0b60 100644 --- a/pydeckard/utils.py +++ b/pydeckard/utils.py @@ -133,8 +133,9 @@ def since(reference) -> str: return " ".join(buff) -def _input(prompt_head, acceptable=None, typus=None): - """ This function is designed to capture the parameters that will be used to configure the bot. +def validate_input(prompt_head, acceptable=None, typus=None): + """ + This function is designed to capture the parameters that will be used to configure the bot. It captures input and validates the data obtained. Steps: Create a text string to use as a prompt. @@ -142,6 +143,14 @@ def _input(prompt_head, acceptable=None, typus=None): Validate the received data. Return the validated data or None. Parameter capture can be interrupted with Ctrl+C + + Arguments + prompt_head (str): Start of the message to be displayed to the user. + acceptable (list/tuple, optional): Whitelist of values or range (min, max). + typus (callable): Data type to convert the input to. + + Return + The data validated and converted to type 'typus' or None """ prompt_tail = '' @@ -149,7 +158,7 @@ def _input(prompt_head, acceptable=None, typus=None): if isinstance(acceptable, list): prompt_tail = f" ({'/'.join(map(str, acceptable))})" elif isinstance(acceptable, tuple): - prompt_tail = f" ({acceptable[0]}-{acceptable[1]})" + prompt_tail = f' ({acceptable[0]}-{acceptable[1]})' prompt = f'{prompt_head}{prompt_tail}: ' @@ -158,29 +167,38 @@ def _input(prompt_head, acceptable=None, typus=None): data = input(prompt).strip() except KeyboardInterrupt: raise - if not (data and typus): + + if not (data and callable(typus)): return None try: - data = typus(data) + if typus is int: + data = int(data, 0) + elif typus is str and acceptable and all(x.isupper() for x in acceptable): + data = data.upper() + elif typus is str or typus is float: + data = typus(data) + else: + return None except ValueError: - print(f'Error: El valor debe ser de tipo {typus.__name__}') + print(f'El valor debe ser de tipo {typus.__name__}') continue if isinstance(acceptable, list) and data not in acceptable: - print(f'Error: El valor debe ser una de estas opciones: {acceptable}') + print(f'El valor debe ser una de estas opciones: {'/'.join(map(str, acceptable))}') continue if isinstance(acceptable, tuple): if not (acceptable[0] <= data <= acceptable[1]): - print(f'Error: El valor debe estar entre {acceptable[0]} y {acceptable[1]}.') + print(f'El valor debe estar entre {acceptable[0]} y {acceptable[1]}.') continue - return str(data) + return data def setup_bot(): - """ A wizard starts to configure the bot and create an automatic startup system based on the operating system. + """ + A wizard starts to configure the bot and create an automatic startup system based on the operating system. It performs an input for each required configuration parameter. The "parameters" list contains all the defined parameters, each as a tuple of four elements: parameter name, @@ -196,20 +214,21 @@ def setup_bot(): env_path = root_path / '.env' system_name = platform.system() - print(f'--- Asistente de configuración para PyDeckard (SO: {system_name}) ---') + 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), + ('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), - ('CHINESE_CHARS', 'Porcentaje de caracteres chinos en username', (0.0, 1.0), float), - ('CHINESE_CHARS', 'Tiempo de retardo para la bienvenida (seg)', 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: _input(*args) for key, *args in parameters} + items_env = {key: validate_input(*args) for key, *args in parameters} except KeyboardInterrupt: print('Asistente cancelado por el usuario.') sys.exit(1) diff --git a/test/test_utils.py b/test/test_utils.py index fc115d6..86687b8 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -5,55 +5,488 @@ import pytest from freezegun import freeze_time -from pydeckard import utils +from pydeckard.utils import validate_input, since @freeze_time("2019-05-16 13:35:16") def test_since_second(): ref = datetime.datetime(2019, 5, 16, 13, 35, 15) - assert utils.since(ref) == "1 second" + assert since(ref) == "1 second" @freeze_time("2019-05-16 13:35:21") def test_since_seconds(): ref = datetime.datetime(2019, 5, 16, 13, 35, 15) - assert utils.since(ref) == "6 seconds" + assert since(ref) == "6 seconds" @freeze_time("2019-05-16 13:36:21") def test_since_minute_and_seconds(): ref = datetime.datetime(2019, 5, 16, 13, 35, 15) - assert utils.since(ref) == "1 minute 6 seconds" + assert since(ref) == "1 minute 6 seconds" @freeze_time("2019-05-16 13:37:21") def test_since_minutes_and_seconds(): ref = datetime.datetime(2019, 5, 16, 13, 35, 15) - assert utils.since(ref) == "2 minutes 6 seconds" + assert since(ref) == "2 minutes 6 seconds" @freeze_time("2019-05-16 14:37:21") def test_since_hour_and_minutes_and_seconds(): ref = datetime.datetime(2019, 5, 16, 13, 35, 15) - assert utils.since(ref) == "1 hour 2 minutes 6 seconds" + assert since(ref) == "1 hour 2 minutes 6 seconds" @freeze_time("2019-05-16 15:37:21") def test_since_hours_and_minutes_and_seconds(): ref = datetime.datetime(2019, 5, 16, 13, 35, 15) - assert utils.since(ref) == "2 hours 2 minutes 6 seconds" + assert since(ref) == "2 hours 2 minutes 6 seconds" @freeze_time("2019-05-17 15:37:21") def test_since_day_hours_and_minutes_and_seconds(): ref = datetime.datetime(2019, 5, 16, 13, 35, 15) - assert utils.since(ref) == "1 day 2 hours 2 minutes 6 seconds" + assert since(ref) == "1 day 2 hours 2 minutes 6 seconds" @freeze_time("2019-05-19 15:37:21") def test_since_days_hours_and_minutes_and_seconds(): ref = datetime.datetime(2019, 5, 16, 13, 35, 15) - assert utils.since(ref) == "3 days 2 hours 2 minutes 6 seconds" + assert since(ref) == "3 days 2 hours 2 minutes 6 seconds" + + +def test_tipo_none0(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '') + + assert validate_input('Dato', None, None) is None + + +def test_tipo_none1(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '1') + + assert validate_input('Dato', None, None) is None + + +def test_tipo_none2(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '1.0') + + assert validate_input('Dato', None, None) is None + + +def test_tipo_none3(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: 'Texto') + + assert validate_input('Dato', None, None) is None + + +def test_tipo_texto0(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '') + + assert validate_input('Dato', None, str) is None + + +def test_tipo_texto1(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '1') + + assert validate_input('Dato', None, str) == '1' + + +def test_tipo_texto2(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '1.0') + + assert validate_input('Dato', None, str) == '1.0' + + +def test_tipo_texto3(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: 'Texto') + + assert validate_input('Dato', None, str) == 'Texto' + + +def test_tipo_int0(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '') + + assert validate_input('Dato', None, int) is None + + +def test_tipo_int1(monkeypatch): + # Decimal + monkeypatch.setattr('builtins.input', lambda _: '1') + + assert validate_input('Dato', None, int) == 1 + + +def test_tipo_int2(monkeypatch): + # Octal + monkeypatch.setattr('builtins.input', lambda _: '0o10') + + assert validate_input('Dato', None, int) == 8 + + +def test_tipo_int3(monkeypatch): + # Hexadecimal + monkeypatch.setattr('builtins.input', lambda _: '0xff') + + assert validate_input('Dato', None, int) == 255 + + +def test_tipo_int4(monkeypatch): + # Binario + monkeypatch.setattr('builtins.input', lambda _: '0b11') + + assert validate_input('Dato', None, int) == 3 + +def test_tipo_int5(monkeypatch): + # Separador _ + monkeypatch.setattr('builtins.input', lambda _: '1_000_000') + + assert validate_input('Dato', None, int) == 1000000 + + +def test_tipo_int6(monkeypatch): + # Cero negativo + monkeypatch.setattr('builtins.input', lambda _: '-0') + + assert validate_input('Dato', None, int) == 0 + + +def test_tipo_int7(monkeypatch): + # Decimal con espacios + monkeypatch.setattr('builtins.input', lambda _: ' 10 ') + + assert validate_input('Dato', None, int) == 10 + +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']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', None, int) + imprime = capsys.readouterr().out + + assert 'El valor debe ser de tipo int' in imprime + assert resultado == 1 + + +def test_tipo_int16(monkeypatch, capsys): + # Texto + entradas = iter(['Texto', '1']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', None, int) + imprime = capsys.readouterr().out + + assert 'El valor debe ser de tipo int' in imprime + assert resultado == 1 + + +def test_tipo_float0(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '') + + assert validate_input('Dato', None, float) is None + + +def test_tipo_float1(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '1') + + assert validate_input('Dato', None, float) == 1.0 + + +def test_tipo_float2(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '1.0') + + assert validate_input('Dato', None, float) == 1.0 + + +def test_tipo_float3(monkeypatch, capsys): + entradas = iter(['Texto', '1.0']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', None, float) + imprime = capsys.readouterr().out + + assert 'El valor debe ser de tipo float' in imprime + assert resultado == 1.0 + + +def test_tipo_float4(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '-0.0') + + assert validate_input('Dato', None, float) == -0.0 + + +def test_rango_tipo_int0(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '') + + assert validate_input('Dato', (0, 1), int) is None + + +def test_rango_tipo_int1(monkeypatch): + # En rango entero + monkeypatch.setattr('builtins.input', lambda _: '1') + + assert validate_input('Dato', (0, 10), int) == 1 + + +def test_rango_tipo_int2(monkeypatch, capsys): + # Fuera de rango entero + entradas = iter(['20', '-2', '1']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', (0, 10), int) + imprime = capsys.readouterr().out + + assert 'Error: El valor debe estar entre 0 y 10' in imprime + assert resultado == 1 + + +def test_rango_tipo_int3(monkeypatch, capsys): + # En rango float + entradas = iter(['5.0', '1']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', (0, 10), int) + imprime = capsys.readouterr().out + + assert 'El valor debe ser de tipo int' in imprime + assert resultado == 1 + + +def test_rango_tipo_int4(monkeypatch, capsys): + # Fuera de rango float + entradas = iter(['20.0', '-1.0', '1']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', (0, 10), int) + imprime = capsys.readouterr().out + + assert 'El valor debe ser de tipo int' in imprime + assert resultado == 1 + + +def test_rango_tipo_int5(monkeypatch, capsys): + # Texto + entradas = iter(['texto', '1']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', (0, 10), int) + imprime = capsys.readouterr().out + + assert 'El valor debe ser de tipo int' in imprime + assert resultado == 1 + + +def test_rango_tipo_float0(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '') + + assert validate_input('Dato', (0.0, 1.0), float) is None + + +def test_rango_tipo_float1(monkeypatch): + # En rango entero + monkeypatch.setattr('builtins.input', lambda _: '1') + + assert validate_input('Dato', (0.0, 1.0), float) == 1.0 + + +def test_rango_tipo_float2(monkeypatch, capsys): + # Fuera de rango entero + entradas = iter(['2', '-2','1.0']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', (0.0, 1.0), float) + imprime = capsys.readouterr().out + + assert 'Error: El valor debe estar entre 0.0 y 1.0' in imprime + assert resultado == 1.0 + + +def test_rango_tipo_float3(monkeypatch): + # En rango float + monkeypatch.setattr('builtins.input', lambda _: '1.0') + + assert validate_input('Dato', (0.0, 1.0), float) == 1.0 + + +def test_rango_tipo_float4(monkeypatch, capsys): + # Fuera de rango float + entradas = iter(['2.0', '-1.0', '1.0']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', (0.0, 1.0), float) + imprime = capsys.readouterr().out + + assert 'Error: El valor debe estar entre 0.0 y 1.0' in imprime + assert resultado == 1.0 + + +def test_rango_tipo_float5(monkeypatch, capsys): + # Texto + entradas = iter(['texto', '1.0']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', (0.0, 1.0), float) + imprime = capsys.readouterr().out + + assert 'El valor debe ser de tipo float' in imprime + assert resultado == 1.0 + + +def test_option_tipo_int0(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '') + + assert validate_input('Dato', [-2, -1, 0, 1, 2, 3], int) is None + + +def test_option_tipo_int1(monkeypatch): + # En lista entero + monkeypatch.setattr('builtins.input', lambda _: '1') + + assert validate_input('Dato', [-2, -1, 0, 1, 2, 3], int) == 1 + + +def test_option_tipo_int2(monkeypatch, capsys): + # Fuera de lista entero + entradas = iter(['20', '-23', '1']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', [-2, -1, 0, 1, 2, 3], int) + imprime = capsys.readouterr().out + + assert 'Error: El valor debe ser una de estas opciones: -2/-1/0/1/2/3' in imprime + assert resultado == 1 + + +def test_option_tipo_int4(monkeypatch, capsys): + # Fuera de lista float + entradas = iter(['20.0', '-10.0', '1']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', [-2, -1, 0, 1, 2, 3], int) + imprime = capsys.readouterr().out + + assert 'El valor debe ser de tipo int' in imprime + assert resultado == 1 + + +def test_option_tipo_int5(monkeypatch, capsys): + # Texto + entradas = iter(['texto', '1']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', [-2, -1, 0, 1, 2, 3], int) + imprime = capsys.readouterr().out + + assert 'El valor debe ser de tipo int' in imprime + assert resultado == 1 + + +def test_option_tipo_int6(monkeypatch, capsys): + # En lista vacia entero + entradas = iter(['texto', '']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', [], int) + imprime = capsys.readouterr().out + + assert 'El valor debe ser de tipo int' in imprime + assert resultado is None + + +def test_option_tipo_texto0(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: '') + + assert validate_input('Dato', ['DEBUG', 'INFO', 'WARNING', 'ERROR'], str) is None + + +def test_option_tipo_texto1(monkeypatch, capsys): + # Entero + entradas = iter(['20', 'DEBUG']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', ['DEBUG', 'INFO', 'WARNING', 'ERROR'], str) + imprime = capsys.readouterr().out + + assert 'Error: El valor debe ser una de estas opciones: DEBUG/INFO/WARNING/ERROR' in imprime + assert resultado == 'DEBUG' + + +def test_option_tipo_texto2(monkeypatch, capsys): + # float + entradas = iter(['20.0', 'DEBUG']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', ['DEBUG', 'INFO', 'WARNING', 'ERROR'], str) + imprime = capsys.readouterr().out + + assert 'Error: El valor debe ser una de estas opciones: DEBUG/INFO/WARNING/ERROR' in imprime + assert resultado == 'DEBUG' + + +def test_option_tipo_texto3(monkeypatch, capsys): + # Fuera de lista texto + entradas = iter(['HOLA', 'DEBUG']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', ['DEBUG', 'INFO', 'WARNING', 'ERROR'], str) + imprime = capsys.readouterr().out + + assert 'Error: El valor debe ser una de estas opciones: DEBUG/INFO/WARNING/ERROR' in imprime + assert resultado == 'DEBUG' + + +def test_option_tipo_texto4(monkeypatch): + # Texto + monkeypatch.setattr('builtins.input', lambda _: 'DEBUG') + + assert validate_input('Dato', ['DEBUG', 'INFO', 'WARNING', 'ERROR'], str) == 'DEBUG' + + +def test_option_tipo_texto5(monkeypatch): + # Texto minúscula + monkeypatch.setattr('builtins.input', lambda _: 'debug') + + assert validate_input('Dato', ['DEBUG', 'INFO', 'WARNING', 'ERROR'], str) == 'DEBUG' + + +def test_option_tipo_texto6(monkeypatch): + # Texto mezcla minúsculas + monkeypatch.setattr('builtins.input', lambda _: 'DebuG') + + assert validate_input('Dato', ['DEBUG', 'INFO', 'WARNING', 'ERROR'], str) == 'DEBUG' + + +def test_option_tipo_texto7(monkeypatch, capsys): + # En lista vacia texto + entradas = iter(['texto', '']) + monkeypatch.setattr("builtins.input", lambda _: next(entradas)) + + resultado = validate_input('Dato', [], str) + imprime = capsys.readouterr().out + + assert 'Error: El valor debe ser una de estas opciones: ' in imprime + assert resultado is None + + +def test_tipo_raro0(monkeypatch): + monkeypatch.setattr('builtins.input', lambda _: 'texto') + + assert validate_input('Dato', None, bool) is None + + +def test_tipo_raro1(monkeypatch): + # Tipo no callable + monkeypatch.setattr('builtins.input', lambda _: 'texto') + + assert validate_input('Dato', None, object()) is None if __name__ == "__main__": From 37b9926b06862e0fdc22812a28b318f3d74af99e Mon Sep 17 00:00:00 2001 From: misanram Date: Sat, 18 Apr 2026 23:24:51 +0100 Subject: [PATCH 28/28] Modificar pyproject.toml para poder instalar las paquetes dev desde con pip .[dev] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e599b3b..f058c9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ ] license = "GPL-3.0-or-later" license-files = ["LICENSE"] -dynamic = ['dependencies', 'readme'] +dynamic = ['dependencies', 'readme', 'optional-dependencies'] version="0.1.7" [project.urls]