Giter Club home page Giter Club logo

cerbottana's Introduction

cerbottana

Python 3.12 Code style: black Tests status Coverage

cerbottana is a Pokémon Showdown bot, originally developed for the room Italiano.

Getting started

These instructions assume you are in the root directory of a cloned cerbottana repository. If you use Windows, it is strongly advised to work within the WSL.

If you are going to test your bot locally, you will also need an active instance of a Pokémon Showdown server.

This project uses Poetry to manage its dependencies, so you will need to install that as well.

Generating complementary files

Copy .env-example into .env and edit the file accordingly. Optional environment variables are commented out.

Running a bot instance

First of all, you need to install the dependencies (drop --only main if you wish to contribute):

poetry install --only main

If you are running a local Pokémon Showdown instance, make sure it is active. Then, to start cerbottana, run:

poetry run cerbottana

To stop the execution just raise a SIGINT (Ctrl + C) in the console.

Contributing

Before submitting a pull request, please make sure that poetry run poe all passes without errors.

The bulk of cerbottana code is plugins (see plugins reference). Working on a simple plugin is a good first contribution.

cerbottana's People

Stargazers

 avatar

Watchers

 avatar

cerbottana's Issues

Linter for docstrings

Darglint is a good option. We may consider removing type hints from docstrings to avoid generating DAR103 false positives for optional arguments.

Remove unused code from CSS stylesheet

At this points it's necessary only for the badges webpage. Most of the code can be removed.
Will do it when I have some spare time tomorrow or later today, if there are no objections.

Unexpected error log in unit testing

pytest produces an unexpected error after tests finish:

Task was destroyed but it is pending!
task: <Task pending coro=<logger_task() running at /home/plato/repos/cerbottana-theta/plugins/chatlog.py:54> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f153e9354f8>()]>>

It occurs both locally and on the latest workflow. This error was introduced by #118, as it wasn't manifested in older workflows.

Show !tier info after a tournament is created

(Requested by Pissog.)
Randomized formats should be excluded.
Will implement this after the previous issues I opened these days.

TODO:
Investigate non-random formats that lack a /tier entry.
Make sure there wouldn't be conflicts with other bots.

clearprofile command

Deletes a phrase added with .setprofile.
This action doesn't need staff approval and should remove any pending unapproved descriptions too.

Thanks @f0ffee for reminding us! For further discussion, see #80.

Code refactoring

Proposta

Di seguito elenco alcune problematiche della codebase attuale e come verrebbero risolte nella ristrutturazione a cui sto lavorando da qualche settimana. Ho già riscritto il codice con i nuovi modelli e qualche plugin attivato; nei prossimi giorni posso aggiungere una branch al mio fork di cerbottana o lavorare direttamente in una branch di questo repository se c'è la disponibilità.

Signature dei comandi

Problema

La funzione che implementa un comando ha la seguente signature:

async def foo(conn: Connection, room: Optional[str], user: str, arg: str) -> None:
    pass

Per accedere ai singoli parametri di un argomento bisogna talvolta splittare la stringa arg; la logica si complica se magari si vuole solo il primo argomento come parametro e il resto come stringa a sé (vd. tell in plugins/misc.py). Sarebbe utile definire la logica per queste azioni in modo centralizzato e riutilizzarla di volta in volta, piuttosto che riscrivere tutto da capo ogni volta.

Ovviamente si potrebbe far passare un argomento in più alle funzioni che definiscono i comandi ma a lungo andare non conviene. Ad ogni possibile aggiunta verrebbe allungata la signature di ogni funzione in tutto il repo con argomenti potenzialmente inutilizzati. Inoltre parametri interdipendenti (es. arg e args) non rimarrebbero sicronizzati in caso di modifica.

Possibile soluzione

Invece di passare una lista di parametri alla funzione, usare un'istanza di una classe che ha come attributi tutti gli argomenti della signature precedente e aggiunge delle proprietà utili generate dinamicamente, tra cui args.

async def foo(msg: Message) -> None:
    ...
    print(msg.arg)
    print(msg.args[3])
    print(msg.user)
    ...

Eccessiva dipendenza da utils

Problema

Semplici operazioni quali controllare se un utente è driver si appoggiano ad un modulo esterno, appesantendo molto la sintassi e aggiugendo una dipendenza che potrebbe essere evitata.

async def foo(conn: Connection, room: Optional[str], user: str, arg: str) -> None:
    if utils.is_driver(room, user):
        ...

Possibile soluzione

Invece di passare semplici stringhe che rappresentano l'utente e la room, passare delle istanze di classi che ne contengono anche le relative proprietà. Tra l'altro una classe del genere già esiste per le room.

async def foo(msg: Message) -> None:
    if not msg.author.is_driver:  # author: User
        return
    ...

Sovraccarico di responsabilità di Connection

Problema

Attualmente esiste una classe Room, eppure codice che concettualmente riguarda una room è relegato all'interno di Connection. Alcuni esempi sono:

  • try_modchat()
  • is_private(conn, room), che è attualmente in utils.py ma potrebbe essere spostata in Connection per rimuovere un parametro superfluo. Non potrebbe però essere inclusa direttamente in Room.

Possibile soluzione

Questo problema deriva dal fatto che Room non può interagire col socket, in quanto ciò renderebbe parzialmente inutile l'astrazione di Connection. Si potrebbe però passare a Room un'istanza di Connection (discorso analogo per User e Message).

Così facendo, try_modchat() e is_private() potrebbero essere definiti direttamente nell'oggetto Room. Inoltre, invece di mantenere in Connection i metodi send_message(), send_pm(), send_reply() questi potrebbero ora essere definiti nei vari oggetti Room, ... come wrapper di un unico comando connection.send().

async def foo(msg: Message) -> None:
    ...
    # Invece di conn.send_message(room, message)
    await msg.room.send("Messaggio broadcastato in una room")

    # Invece di conn.send_pm(user, message)
    await msg.author.send("Ciao ti scrivo in PM")

    # Invece di conn.send_reply(room, user, message)
    await msg.reply("Forse in room, forse in pm!")

Problematiche della nuova struttura

User è contestuale a Room

Attualmente gli utenti sono memorizzati all'interno di Room in una lista di stringhe. Si potrebbe fare lo stesso con le istanze di User, ma all'interno di una funzione si rischierebbe di avere più istanze che descrivono lo stesso utente in relazione a room differenti. Un esempio è una funzione decorata con @parametrize_room.

Ciò potrebbe risultare confusionario sia nella sintassi, sia nell'organizzazione del codice: nuovi metodi di User quali can_pminfobox_to non sono contestuali alla room di appartenenza e attributi quali global_rank e idle devono essere propagati ad ogni room user.

Update `.sprites` logic

Rely on PS data instead of trying to separate base species / forme suffix heuristically.
Also add generic utility functions to access PS data; will be needed at least for #192 too.

Proper setup for custom username colors data

The current implementation hardcodes custom username colors in a dictionary updated on a daily basis through a GH workflow. Furthermore, this dictionary is stored in a source file, so all the automatically generated commits are mixed with actual contributions.

Since the introduction of the workflow (84ff01d), these "Update custom username colors" commits have slightly outnumbered the actual contributions committed by the code owner, without considering commits that got reverted back.

I'm opening this issue because I don't think this is a feasible setup in the long run and I'm sure this was supposed to be a temporary solution anyway. I'm not interested in working on it myself as of now, but I'll write down some insights and might add other comments in the future.

As a baseline, we should rely on the newly introduced <username> tag whenever it's possible. If we'll still need to store custom username colors, we should retrieve data dynamically after startup: cerbottana is restarted often and not having an updated username color isn't much impactful anyway.

Undefined behaviour for setprofile

Writing .setprofile without any arguments doesn't seem to affect the dashboard but cerbottana still displays a rank htmlbox, prompting staff members to review a profile phrase request.

We should either use this syntax to remove a profile phrase or consider it invalid input and avoid sending the rank htmlbox. If we choose the latter I'll just add a small commit to my next PR.

Update modchat handler

To detect automodchat.
I'll wait a few days to make sure everything works properly and isn't subjected to changes.

Support custom ranks

We received the request to support bot commands written by users with a custom rank. This is useful for a private room.
Parnassius told me to consider all non-alphanumeric characters that aren't already used for an official rank as valid custom ranks.

I'm opening an issue because I created a mock implementation but I didn't get any feedback privately, and I'd rather discuss the implementation details before opening the PR.

Should I...

  • Broaden the voice role of utils.has_role to include custom ranks. That's honestly the most straightforward and fitting approach in my opinion.
  • Create a new role for utils.has_role that includes custom ranks alongside regular ones.
  • Edit only scope_checker (plugins/__init__.py) without modifying or creating a role.

htmlbox links vgc pike tour

HI!, i'm a newcomer in the world of programming and i wanted to try doing a thing. I want to create a box with cute links for a vgc tour hosted this weekend, lmk. Best Regards

Fix modchat feature

Upon joining a room, cerbottana doesn't detect if modchat + is already set unless the action is logged in the chat.

I'm not delving into the details because I already discussed about this issue with @Parnassius and he's working on a fix.

Room-specific permissions for bot commands

Room Owners should be able to disable bot commands and customize permission levels.

This feature requires changes to plugins/__init__.py and a new database table to persist data, so I'll work on this after #99 is merged to avoid conflicts.

Return error messages when a tour can't be created

Sometimes, when trying to create a tour with a bot command, nothing will happen.

It would be nice if there was some output explaining why the tour can't be created, for example because the server is restarting soon, or because there's another game in progress in the room.

Fix quotes command for rooms without quotes

.quotes is not supposed to link a webpage if there aren't any saved quotes.

After the SQLAlchemy rewrite, the conditional statement that checks whether a room has any quotes always evaluates to True. This should be an easy bugfix.

Per-room eightball answers

I already pushed the alemic script to per-room-eightball-wip, I'm creating this issue just to jot down some notes. I'd probably wait for #164 to be merged before working on this.

  • Default answers should either:
    • be excluded from the database, and hardcoded in the .eightball command, taking the room language into consideration
    • be inserted into the database when needed (probably when someone tries to use .eightball and the database has no records?)
  • .addeightballanswer and .removeeightballanswer should use parametrize_room instead of main_room_only, and should log in the room modlog if the commands were made in pms
  • .removeeightballanswerid should use parametrize_room as well, and always log to the room modlog
  • the htmlpage should be available for room drivers, and maybe in read only mode for everyone else?

Code re-organization

In case you agree, I created commits for all of these points (https://github.com/palt0/cerbottana/commits/code-reorganization).

Source files should have source code (crazy, I know)

  • Move utils.AVATAR_IDS to data/.
  • Move the dict of letters for .say to data/. The main drawback for this would be losing readability (unless you don't want to store it in JSON), which isn't a big deal since we woudn't edit that data anyway.

Other possible changes

  • Move .dashboard in plugins.misc. At this point it's just a legacy message, it doesn't warrant a module of its own. Or are you planning to expand this feature in the future? Remove .dashboard.

Remove features implemented natively on PS

Some of our current features are now supported natively on PS.

Features that became redundant and should be removed

  • Automatic modchat: Will be implemented on PS in the near future and I prepared a commit to remove it, it's just a matter of time.
  • Chatlog: Last time we discussed about this feature, we (both) seemed to be in favor of removing it, is it still needed?

Features that should be kept, despite being now supported natively

  • Quotes
  • Repeats: We prefer using the PS commands but we still use ours to throttle specific commands that aren't supported natively.

CAP Pokémon are not shown in `.sprite`

CAP Pokémon are not considered when using the .sprite command. The bot will output an error of "Pokémon not found".

I'm not sure if they should be considered for .randsprite, but it definitely should be allowed to display their sprites when called individually.

Python 3.8 stylistic changes

List of stylistic code changes that I should consider (and implement) after #203 is merged.

  • Use walrus operator for simple conditional constructs (investigate).
  • Use self-documented f-strings for repeats logs. Use Repeat.__str__ instead of writing the logic in Repeat.__init__.

I'll update this list if something else comes to mind.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.