Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6f547f9
Reorganizar los ficheros y ajustar los import
misanram Apr 18, 2026
b15e463
Ajustar los import de los test
misanram Apr 18, 2026
e504a0b
Ajustar .gitignore
misanram Apr 18, 2026
413d3e6
Actualizar .travis.yml
misanram Apr 18, 2026
50fdfb1
Rehacer y actualizar pyproject.toml
misanram Apr 18, 2026
2ce109f
Actualizar README.md
misanram Apr 18, 2026
3058685
Se crea la funcion main en bot.py para arrancar el bot.
misanram Apr 18, 2026
36b8486
Mínimos ajustes en el README.md
misanram Apr 18, 2026
635a9a7
Corrección en constate REPLIES para c++
misanram Apr 18, 2026
c9963d7
Corrección en README.md
misanram Apr 19, 2026
94d57ef
Se crea fichero __init__.py para módulo pydeckard
misanram Apr 19, 2026
8a81576
Corrección en README.md
misanram Apr 19, 2026
edd8b65
Se añade la opción setup a bot.py, y se crean las funciones setup_bot…
misanram Apr 19, 2026
27bc70d
Se documento el proceso de setup en README.md
misanram Apr 19, 2026
9804bfe
Se documento el proceso de setup en README.md
misanram Apr 19, 2026
d84c64e
Se elimina el logueo en archivo y la opción verbose,
misanram Apr 19, 2026
42e47b8
Se crean los test para las nuevas funciones. Se completa la documenta…
misanram Apr 19, 2026
41825ab
Corrección de errores en util.py y en README.md
misanram Apr 19, 2026
4d0fc9d
Mínimos cambios enla función setup_bot de utils.py
misanram Apr 19, 2026
4aeacbe
Mínimos ajuste en la función get_options de bot.py
misanram Apr 19, 2026
4b0540d
Se cambia el nombre del Entry Point y del logger a pydeckard. Se docu…
misanram Apr 19, 2026
d453741
Se cambia la función setup_bot en utils para usar pydeckard como ejec…
misanram Apr 19, 2026
988c042
Corrección en nombre de clave "BOT_VERBOSITY" en setup_bot.
misanram Apr 19, 2026
67a19c8
Mejorando documentación en README.md
misanram Apr 19, 2026
e8c841f
Merge branch 'master' into Nueva-opción-setup
euribates Apr 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
bot.log
.atico/
.env
bot.log
.env
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ install:
- pipenv install -d
script:
- pytest
- flake8
- flake8
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<token of your dev bot>"' > .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:

Expand All @@ -56,4 +66,4 @@ Use pytest:

~~~console
$ python3 -m pytest
~~~
~~~
36 changes: 20 additions & 16 deletions pydeckard/bot.py
Original file line number Diff line number Diff line change
@@ -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():
Expand All @@ -27,33 +28,29 @@ 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)

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]
Expand Down Expand Up @@ -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. ")
Expand Down Expand Up @@ -186,8 +185,13 @@ def run(self):


def main():
"""
Arranca el bot
"""

bot = DeckardBot()
bot.run()


if __name__ == "__main__":
main()
main()
6 changes: 5 additions & 1 deletion pydeckard/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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! 🐫",
Expand All @@ -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
85 changes: 44 additions & 41 deletions pydeckard/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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}
Expand All @@ -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)
6 changes: 2 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
6 changes: 3 additions & 3 deletions test/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand All @@ -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'])
Expand Down
Loading