diff --git a/README.md b/README.md index c26d66e0..07ef8773 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,28 @@ A multipurpose bot framework and webserver - [Telegram](https://core.telegram.org/bots) - [Discord](https://discordapp.com/developers/docs/) -- [Matrix]() (no E2E support yet) +- [Matrix](https://matrix.org/) (no E2E support yet) ## Installing +To install `royalnet`, run: +``` +pip install royalnet +``` + +To install a specific module, run: +``` +pip install royalnet[MODULENAME] +``` + To install all `royalnet` modules, run: - ``` -royalnet[telegram,discord,alchemy_easy,bard,constellation,sentry,herald,coloredlogs] +pip install royalnet[telegram,discord,matrix,alchemy_easy,bard,constellation,sentry,herald,coloredlogs] ``` -> You will soon be able to install only the modules you need instead of the full package, but the feature isn't ready yet... +## Documentation + +`royalnet`'s documentation is available [here](https://gh.steffo.eu/royalnet). ## Developing `royalnet` @@ -41,13 +52,5 @@ cd royalnet And finally install all dependencies and the package: ``` -poetry install -E telegram -E discord -E alchemy_easy -E bard -E constellation -E sentry -E herald -E coloredlogs -``` - -## Developing `royalnet` packages - -See the [royalnet-pack-template](https://github.com/Steffo99/royalnet-pack-template) project. - -## Documentation - -`royalnet`'s documentation is available [here](https://gh.steffo.eu/royalnet). +poetry install -E telegram -E discord -E matrix -E alchemy_easy -E bard -E constellation -E sentry -E herald -E coloredlogs +``` \ No newline at end of file diff --git a/docs/doctrees/apireference.doctree b/docs/doctrees/apireference.doctree index 1a972778..d680d3dc 100644 Binary files a/docs/doctrees/apireference.doctree and b/docs/doctrees/apireference.doctree differ diff --git a/docs/doctrees/environment.pickle b/docs/doctrees/environment.pickle index 1c754a48..4f557d01 100644 Binary files a/docs/doctrees/environment.pickle and b/docs/doctrees/environment.pickle differ diff --git a/docs/doctrees/index.doctree b/docs/doctrees/index.doctree index 8d397d5e..92d04b5b 100644 Binary files a/docs/doctrees/index.doctree and b/docs/doctrees/index.doctree differ diff --git a/docs/doctrees/packs/command.doctree b/docs/doctrees/packs/command.doctree new file mode 100644 index 00000000..eaa917e4 Binary files /dev/null and b/docs/doctrees/packs/command.doctree differ diff --git a/docs/doctrees/packs/event.doctree b/docs/doctrees/packs/event.doctree new file mode 100644 index 00000000..9574a22a Binary files /dev/null and b/docs/doctrees/packs/event.doctree differ diff --git a/docs/doctrees/packs/pack.doctree b/docs/doctrees/packs/pack.doctree new file mode 100644 index 00000000..5b106f60 Binary files /dev/null and b/docs/doctrees/packs/pack.doctree differ diff --git a/docs/doctrees/packs/star.doctree b/docs/doctrees/packs/star.doctree new file mode 100644 index 00000000..0327d8c4 Binary files /dev/null and b/docs/doctrees/packs/star.doctree differ diff --git a/docs/doctrees/packs/table.doctree b/docs/doctrees/packs/table.doctree new file mode 100644 index 00000000..053913ea Binary files /dev/null and b/docs/doctrees/packs/table.doctree differ diff --git a/docs/html/.buildinfo b/docs/html/.buildinfo index 6b47252a..4e51bfe5 100644 --- a/docs/html/.buildinfo +++ b/docs/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 444318501c4e921b64eae4e12bd3ce08 +config: 59f01b51bca09082ff992b52b6e98cfc tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/html/_sources/index.rst.txt b/docs/html/_sources/index.rst.txt index e2605ae8..1e5a928f 100644 --- a/docs/html/_sources/index.rst.txt +++ b/docs/html/_sources/index.rst.txt @@ -1,4 +1,4 @@ -royalnet +Royalnet ==================================== Welcome to the documentation of Royalnet! @@ -6,6 +6,7 @@ Welcome to the documentation of Royalnet! .. toctree:: :maxdepth: 5 + packs/pack randomdiscoveries apireference diff --git a/docs/html/_sources/packs/command.rst.txt b/docs/html/_sources/packs/command.rst.txt new file mode 100644 index 00000000..652c57be --- /dev/null +++ b/docs/html/_sources/packs/command.rst.txt @@ -0,0 +1,350 @@ +.. currentmodule:: royalnet.commands + +Adding a Command to the Pack +==================================== + +A Royalnet Command is a small script that is run whenever a specific message is sent to a Royalnet interface. + +A Command code looks like this: :: + + import royalnet.commands as rc + + class PingCommand(rc.Command): + name = "ping" + + description = "Play ping-pong with the bot." + + def __init__(self, interface): + # This code is run just once, while the bot is starting + super().__init__() + + async def run(self, args: rc.CommandArgs, data: rc.CommandData): + # This code is run every time the command is called + await data.reply("Pong!") + +Creating a new Command +------------------------------------ + +First, think of a ``name`` for your command. +It's the name your command will be called with: for example, the "spaghetti" command will be called by typing **/spaghetti** in chat. +Try to keep the name as short as possible, while staying specific enough so no other command will have the same name. + +Next, create a new Python file with the ``name`` you have thought of. +The previously mentioned "spaghetti" command should have a file called ``spaghetti.py``. + +Then, in the first row of the file, import the :class:`Command` class from royalnet, and create a new class inheriting from it: :: + + import royalnet.commands as rc + + class SpaghettiCommand(rc.Command): + ... + +Inside the class, override the attributes ``name`` and ``description`` with respectively the **name of the command** and a **small description of what the command will do**: :: + + import royalnet.commands as rc + + class SpaghettiCommand(rc.Command): + name = "spaghetti" + + description = "Send a spaghetti emoji in the chat." + +Now override the :meth:`Command.run` method, adding the code you want the bot to run when the command is called. + +To send a message in the chat the command was called in, you can use the :meth:`CommandData.reply` coroutine: :: + + import royalnet.commands as rc + + class SpaghettiCommand(rc.Command): + name = "spaghetti" + + description = "Send a spaghetti emoji in the chat." + + async def run(self, args: rc.CommandArgs, data: rc.CommandData): + await data.reply("🍝") + +And... it's done! The command is ready to be :doc:`added to your pack `! + +Command arguments +------------------------------------ + +A command can have some arguments passed by the user: for example, on Telegram an user may type `/spaghetti carbonara al-dente` +to pass the :class:`str` `"carbonara al-dente"` to the command code. + +These arguments can be accessed in multiple ways through the ``args`` parameter passed to the :meth:`Command.run` +method. + +If you want your command to use arguments, override the ``syntax`` class attribute with a brief description of the +syntax of your command, possibly using {curly braces} for required arguments and [square brackets] for optional +ones. :: + + import royalnet.commands as rc + + class SpaghettiCommand(rc.Command): + name = "spaghetti" + + description = "Send a spaghetti emoji in the chat." + + syntax = "(requestedpasta)" + + async def run(self, args: rc.CommandArgs, data: rc.CommandData): + await data.reply(f"🍝 Here's your {args[0]}!") + + +Direct access +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can consider arguments as if they were separated by spaces. + +You can then access command arguments directly by number as if the args object was a list of :class:`str`. + +If you request an argument with a certain number, but the argument does not exist, an +:exc:`.InvalidInputError` is raised, making the arguments accessed in this way **required**. :: + + args[0] + # "carbonara" + + args[1] + # "al-dente" + + args[2] + # raises InvalidInputError + +Optional access +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you don't want arguments to be required, you can access them through the :meth:`CommandArgs.optional` method: it +will return ``None`` if the argument wasn't passed, making it **optional**. :: + + args.optional(0) + # "carbonara" + + args.optional(1) + # "al-dente" + + args.optional(2) + # None + +You can specify a default result too, so that the method will return it instead of returning ``None``: :: + + args.optional(2, default="banana") + # "banana" + +Full string +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want the full argument string, you can use the :meth:`CommandArgs.joined` method. :: + + args.joined() + # "carbonara al-dente" + +You can specify a minimum number of arguments too, so that an :exc:`.InvalidInputError` will be +raised if not enough arguments are present: :: + + args.joined(require_at_least=3) + # raises InvalidInputError + +Regular expressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For more complex commands, you may want to get arguments through `regular expressions `_. + +You can then use the :meth:`CommandArgs.match` method, which tries to match a pattern to the command argument string, +which returns a tuple of the matched groups and raises an :exc:`.InvalidInputError` if there is no match. + +To match a pattern, :func:`re.match` is used, meaning that Python will try to match only at the beginning of the string. :: + + args.match(r"(carb\w+)") + # ("carbonara",) + + args.match(r"(al-\w+)") + # raises InvalidInputError + + args.match(r"\s*(al-\w+)") + # ("al-dente",) + + args.match(r"\s*(carb\w+)\s*(al-\w+)") + # ("carbonara", "al-dente") + +Raising errors +--------------------------------------------- + +If you want to display an error message to the user, you can raise a :exc:`.CommandError` using the error message as argument: :: + + if not kitchen.is_open(): + raise CommandError("The kitchen is closed. Come back later!") + +There are some subclasses of :exc:`.CommandError` that can be used for some more specific cases: + +:exc:`.UserError` + The user did something wrong, it is not a problem with the bot. + +:exc:`.InvalidInputError` + The arguments the user passed to the command by the user are invalid. + Displays the command syntax in the error message. + +:exc:`.UnsupportedError` + The command is not supported on the interface it is being called. + +:exc:`.ConfigurationError` + The ``config.toml`` file was misconfigured (a value is missing or invalid). + +:exc:`.ExternalError` + An external API the command depends on is unavailable or returned an error. + +:exc:`.ProgramError` + An error caused by a programming mistake. Equivalent to :exc:`AssertionError`, but includes a message to facilitate debugging. + +Coroutines and slow operations +------------------------------------ + +You may have noticed that in the previous examples we used ``await data.reply("🍝")`` instead of just ``data.reply("🍝")``. + +This is because :meth:`CommandData.reply` isn't a simple method: it is a coroutine, a special kind of function that +can be executed separately from the rest of the code, allowing the bot to do other things in the meantime. + +By adding the ``await`` keyword before the ``data.reply("🍝")``, we tell the bot that it can do other things, like +receiving new messages, while the message is being sent. + +You should avoid running slow normal functions inside bot commands, as they will stop the bot from working until they +are finished and may cause bugs in other parts of the code! :: + + async def run(self, args, data): + # Don't do this! + image = download_1_terabyte_of_spaghetti("right_now", from="italy") + ... + +If the slow function you want does not cause any side effect, you can wrap it with the :func:`royalnet.utils.asyncify` +function: :: + + async def run(self, args, data): + # If the called function has no side effect, you can do this! + image = await asyncify(download_1_terabyte_of_spaghetti, "right_now", from="italy") + ... + +Avoid using :func:`time.sleep` function, as it is considered a slow operation: use instead :func:`asyncio.sleep`, +a coroutine that does the same exact thing but in an asyncronous way. + +Delete the invoking message +------------------------------------ + +The invoking message of a command is the message that the user sent that the bot recognized as a command; for example, +the message ``/spaghetti carbonara`` is the invoking message for the ``spaghetti`` command run. + +You can have the bot delete the invoking message for a command by calling the :class:`CommandData.delete_invoking` +method: :: + + async def run(self, args, data): + await data.delete_invoking() + +Not all interfaces support deleting messages; by default, if the interface does not support deletions, the call is +ignored. + +You can have the method raise an error if the message can't be deleted by setting the ``error_if_unavailable`` parameter +to True: :: + + async def run(self, args, data): + try: + await data.delete_invoking(error_if_unavailable=True) + except royalnet.error.UnsupportedError: + await data.reply("🚫 The message could not be deleted.") + else: + await data.reply("βœ… The message was deleted!") + +Using the database +------------------------------------ + +Bots can be connected to a PostgreSQL database through a special SQLAlchemy interface called +:class:`royalnet.alchemy.Alchemy`. + +If the connection is established, the ``self.alchemy`` and ``data.session`` fields will be +available for use in commands. + +``self.alchemy`` is an instance of :class:`royalnet.alchemy.Alchemy`, which contains the +:class:`sqlalchemy.engine.Engine`, metadata and tables, while ``data.session`` is a +:class:`sqlalchemy.orm.session.Session`, and can be interacted in the same way as one. + +Querying the database +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can :class:`sqlalchemy.orm.query.Query` the database using the SQLAlchemy ORM. + +The SQLAlchemy tables can be found inside :class:`royalnet.alchemy.Alchemy` with the :meth:`royalnet.alchemy.Alchemy.get` method: :: + + import royalnet.backpack.tables as rbt + User = self.alchemy.get(rbt.User) + +Adding filters to the query +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can filter the query results with the :meth:`sqlalchemy.orm.query.Query.filter` method. + +.. note:: Remember to always use a table column as first comparision element, as it won't work otherwise. + +:: + + query = query.filter(User.role == "Member") + + +Ordering the results of a query +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can order the query results in **ascending order** with the :meth:`sqlalchemy.orm.query.Query.order_by` method. :: + + query = query.order_by(User.username) + +Additionally, you can append the `.desc()` method to a table column to sort in **descending order**: :: + + query = query.order_by(User.username.desc()) + +Fetching the results of a query +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can fetch the query results with the :meth:`sqlalchemy.orm.query.Query.all`, +:meth:`sqlalchemy.orm.query.Query.first`, :meth:`sqlalchemy.orm.query.Query.one` and +:meth:`sqlalchemy.orm.query.Query.one_or_none` methods. + +Remember to use :func:`royalnet.utils.asyncify` when fetching results, as it may take a while! + +Use :meth:`sqlalchemy.orm.query.Query.all` if you want a :class:`list` of **all results**: :: + + results: list = await asyncify(query.all) + +Use :meth:`sqlalchemy.orm.query.Query.first` if you want **the first result** of the list, or ``None`` if +there are no results: :: + + result: typing.Union[..., None] = await asyncify(query.first) + +Use :meth:`sqlalchemy.orm.query.Query.one` if you expect to have **a single result**, and you want the command to +raise an error if any different number of results is returned: :: + + result: ... = await asyncify(query.one) # Raises an error if there are no results or more than a result. + +Use :meth:`sqlalchemy.orm.query.Query.one_or_none` if you expect to have **a single result**, or **nothing**, and +if you want the command to raise an error if the number of results is greater than one. :: + + result: typing.Union[..., None] = await asyncify(query.one_or_none) # Raises an error if there is more than a result. + +More Alchemy +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can read more about :mod:`sqlalchemy` at their `website `_. + +Calling Events +------------------------------------ + +This section is not documented yet. + +Displaying Keyboards +------------------------------------ + +This section is not documented yet. + +Running code at the initialization of the bot +--------------------------------------------- + +This section is not documented yet. + +Running repeating jobs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This section is not documented yet. \ No newline at end of file diff --git a/docs/html/_sources/packs/event.rst.txt b/docs/html/_sources/packs/event.rst.txt new file mode 100644 index 00000000..9b4f80a9 --- /dev/null +++ b/docs/html/_sources/packs/event.rst.txt @@ -0,0 +1,4 @@ +Using Events +==================================== + +This section is not documented yet. \ No newline at end of file diff --git a/docs/html/_sources/packs/pack.rst.txt b/docs/html/_sources/packs/pack.rst.txt new file mode 100644 index 00000000..1fee19e0 --- /dev/null +++ b/docs/html/_sources/packs/pack.rst.txt @@ -0,0 +1,12 @@ +Creating a Royalnet Pack +==================================== + +This section is not documented yet. + +.. toctree:: + :maxdepth: 5 + + command + star + event + table diff --git a/docs/html/_sources/packs/star.rst.txt b/docs/html/_sources/packs/star.rst.txt new file mode 100644 index 00000000..ae07f784 --- /dev/null +++ b/docs/html/_sources/packs/star.rst.txt @@ -0,0 +1,4 @@ +Adding a Star to the Pack +==================================== + +This section is not documented yet. diff --git a/docs/html/_sources/packs/table.rst.txt b/docs/html/_sources/packs/table.rst.txt new file mode 100644 index 00000000..9cef0afd --- /dev/null +++ b/docs/html/_sources/packs/table.rst.txt @@ -0,0 +1,4 @@ +Using Tables and databases +==================================== + +This section is not documented yet. diff --git a/docs/html/_static/documentation_options.js b/docs/html/_static/documentation_options.js index 7efcfa65..79964755 100644 --- a/docs/html/_static/documentation_options.js +++ b/docs/html/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '5.3.2', + VERSION: '5.4a2', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/html/apireference.html b/docs/html/apireference.html index d95418be..10194e00 100644 --- a/docs/html/apireference.html +++ b/docs/html/apireference.html @@ -8,7 +8,7 @@ - API Reference — Royalnet 5.3.2 documentation + API Reference — Royalnet 5.4a2 documentation @@ -60,7 +60,7 @@
- 5.3.2 + 5.4a2
@@ -85,6 +85,7 @@
    +
  • Creating a Royalnet Pack
  • Random discoveries
  • API Reference
    • Alchemy
    • @@ -166,7 +167,17 @@

      This page is autogenerated from the docstrings inside the code.

      AlchemyΒΆ

      -

      Relational database classes and methods.

      +

      The subpackage providing all functions and classes related to databases and tables.

      +

      It requires either the alchemy_easy or the alchemy_hard extras to be installed.

      +

      You can install alchemy_easy with:

      +
      pip install royalnet[alchemy_easy]
      +
      +
      +

      To install alchemy_hard, refer to the `psycopg2 <https://pypi.org/project/psycopg2/>}`_ installation instructions, +then run:

      +
      pip install royalnet[alchemy_hard]
      +
      +
      class royalnet.alchemy.Alchemy(database_uri: str, tables: Set)ΒΆ
      @@ -262,10 +273,15 @@ Check the tables submodule for more details.

      BackpackΒΆ

      A Pack that is imported by default by all Royalnet instances.

      -

      Keep things here to a minimum!

      BardΒΆ

      +

      The subpackage providing all classes related to music files.

      +

      It requires the bard extra to be installed (the ffmpeg_python, youtube_dl and eyed3 packages).

      +

      You can install it with:

      +
      pip install royalnet[bard]
      +
      +
      class royalnet.bard.YtdlInfo(info: Dict[str, Any])ΒΆ
      @@ -367,92 +383,34 @@ Check the tables submodule for more details.

      -
      -
      -class royalnet.bard.YtdlMp3(ytdl_file: royalnet.bard.ytdlfile.YtdlFile)ΒΆ
      -

      A representation of a YtdlFile conversion to mp3.

      -
      -
      -async convert_to_mp3() → NoneΒΆ
      -

      Convert the file to mp3 with ffmpeg.

      +
      +
      +exception royalnet.bard.BardErrorΒΆ
      +

      Base class for bard errors.

      -
      -
      -async delete_asap() → NoneΒΆ
      -

      Delete the mp3 file.

      +
      +
      +exception royalnet.bard.YtdlErrorΒΆ
      +

      Base class for errors caused by youtube_dl.

      -
      -
      -classmethod from_url(url, **ytdl_args) → List[royalnet.bard.ytdlmp3.YtdlMp3]ΒΆ
      -

      Create a list of YtdlMp3 from a URL.

      +
      +
      +exception royalnet.bard.NotFoundErrorΒΆ
      +

      The requested resource wasn’t found.

      -
      -
      -property infoΒΆ
      -

      Shortcut to get the YtdlInfo of the object.

      -
      - -
      -
      -property is_convertedΒΆ
      -

      Has the file been converted?

      -
      - -
      - -
      -
      -class royalnet.bard.YtdlDiscord(ytdl_file: royalnet.bard.ytdlfile.YtdlFile)ΒΆ
      -

      A representation of a YtdlFile conversion to the discord PCM format.

      -
      -
      -async convert_to_pcm() → NoneΒΆ
      -

      Convert the file to pcm with ffmpeg.

      -
      - -
      -
      -async delete_asap() → NoneΒΆ
      -

      Delete the mp3 file.

      -
      - -
      -
      -embed() → discord.embeds.EmbedΒΆ
      -

      Return this info as a discord.Embed.

      -
      - -
      -
      -classmethod from_url(url, **ytdl_args) → List[royalnet.bard.ytdldiscord.YtdlDiscord]ΒΆ
      -

      Create a list of YtdlMp3 from a URL.

      -
      - -
      -
      -property infoΒΆ
      -

      Shortcut to get the YtdlInfo of the object.

      -
      - -
      -
      -property is_convertedΒΆ
      -

      Has the file been converted?

      -
      - -
      -
      -spawn_audiosource()ΒΆ
      -
      - +
      +
      +exception royalnet.bard.MultipleFilesErrorΒΆ
      +

      The resource contains multiple media files.

      CommandsΒΆ

      +

      The subpackage providing all classes related to Royalnet commands.

      class royalnet.commands.CommandInterface(config: Dict[str, Any])ΒΆ
      @@ -515,6 +473,17 @@ Check the tables submodule for more details.

      A reference to a TelegramSerf.

      +
      +
      +property tableΒΆ
      +

      A shortcut for serf.alchemy.get().

      +
      +
      Raises
      +

      UnsupportedError – if alchemy is None.

      +
      +
      +
      +
      @@ -631,12 +600,14 @@ That probably means, the database row identifying the user.

      async session_close()ΒΆ
      -
      +

      Asyncronously close the session of this object.

      +
      async session_commit()ΒΆ
      -
      +

      Asyncronously commit the session of this object.

      +
      @@ -850,8 +821,17 @@ down).

      ConstellationΒΆ

      -

      The part of royalnet that handles the webserver and webpages.

      -

      It uses many features of starlette.

      +

      The subpackage providing all functions and classes that handle the webserver and the webpages.

      +

      It requires the constellation extra to be installed (starlette).

      +

      You can install it with:

      +
      pip install royalnet[constellation]
      +
      +
      +

      It optionally uses the sentry extra for error reporting.

      +

      You can install them with:

      +
      pip install royalnet[constellation,sentry]
      +
      +
      class royalnet.constellation.Constellation(alchemy_cfg: Dict[str, Any], herald_cfg: Dict[str, Any], packs_cfg: Dict[str, Any], constellation_cfg: Dict[str, Any], logging_cfg: Dict[str, Any])ΒΆ
      @@ -1075,6 +1055,13 @@ or the

      HeraldΒΆ

      +

      The subpackage providing all functions and classes to handle communication between process (even over the Internet).

      +

      It is based on websockets.

      +

      It requires the herald extra to be installed.

      +

      You can install it with:

      +
      pip install royalnet[herald]
      +
      +
      class royalnet.herald.Config(name: str, address: str, port: int, secret: str, secure: bool = False, path: str = '/')ΒΆ
      @@ -1367,6 +1354,7 @@ Akin to the sequence number on IP packets.

      SerfΒΆ

      +

      The subpackage providing all Serf implementations.

      class royalnet.serf.Serf(loop: asyncio.events.AbstractEventLoop, alchemy_cfg: Dict[str, Any], herald_cfg: Dict[str, Any], packs_cfg: Dict[str, Any], **_)ΒΆ
      @@ -1691,53 +1679,6 @@ table and the identity table.

      -
      -
      -class royalnet.utils.FileAudioSource(file)ΒΆ
      -

      A discord.AudioSource that uses a io.BufferedIOBase as an input instead of memory.

      -

      The stream should be in the usual PCM encoding.

      -
      -

      Warning

      -

      This AudioSource will consume (and close) the passed stream.

      -
      -
      -
      -__init__(file)ΒΆ
      -

      Create a FileAudioSource.

      -
      -
      Parameters
      -

      file – the file to be played back.

      -
      -
      -
      - -
      -
      -is_opus()ΒΆ
      -

      This audio file isn’t Opus-encoded, but PCM-encoded.

      -
      -
      Returns
      -

      False.

      -
      -
      -
      - -
      -
      -read()ΒΆ
      -

      Reads 20ms of audio.

      -

      If the stream has ended, then return an empty bytes-like object.

      -
      - -
      -
      -stop()ΒΆ
      -

      Stop the FileAudioSource. Once stopped, a FileAudioSource will immediatly stop reading more bytes from the -file.

      -
      - -
      -
      royalnet.utils.init_sentry(sentry_cfg: Dict[str, Any])ΒΆ
      diff --git a/docs/html/genindex.html b/docs/html/genindex.html index 0d919e52..13177ad1 100644 --- a/docs/html/genindex.html +++ b/docs/html/genindex.html @@ -9,7 +9,7 @@ - Index — Royalnet 5.3.2 documentation + Index — Royalnet 5.4a2 documentation @@ -60,7 +60,7 @@
      - 5.3.2 + 5.4a2
      @@ -85,6 +85,7 @@ @@ -192,8 +193,6 @@
    • (royalnet.commands.Event method)
    • (royalnet.herald.Package method) -
    • -
    • (royalnet.utils.FileAudioSource method)
@@ -240,10 +239,12 @@

B

@@ -272,6 +273,8 @@
  • config (royalnet.commands.CommandInterface attribute)
  • + + - - +
    -
    • normal() (royalnet.utils.MultiLock method) +
    • +
    • NotFoundError
    • numberemojiformat() (in module royalnet.utils)
    • @@ -626,8 +599,6 @@

      R

        +
      • Session() (royalnet.constellation.Star property) +
      • session_acm() (royalnet.alchemy.Alchemy method)
          @@ -757,14 +728,10 @@
        • shoot() (in module royalnet.constellation)
        • sleep_until() (in module royalnet.utils) -
        • -
        • spawn_audiosource() (royalnet.bard.YtdlDiscord method)
        • Star (class in royalnet.constellation)
        • starlette (royalnet.constellation.Constellation attribute) -
        • -
        • stop() (royalnet.utils.FileAudioSource method)
        • syntax (royalnet.commands.Command attribute)
        • @@ -774,6 +741,8 @@

          T

          diff --git a/docs/html/index.html b/docs/html/index.html index b80cbf91..c4bfae23 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -8,7 +8,7 @@ - royalnet — Royalnet 5.3.2 documentation + Royalnet — Royalnet 5.4a2 documentation @@ -36,7 +36,7 @@ - + @@ -60,7 +60,7 @@
          - 5.3.2 + 5.4a2
          @@ -85,6 +85,7 @@ @@ -132,7 +133,7 @@
        • Docs »
        • -
        • royalnet
        • +
        • Royalnet
        • @@ -152,10 +153,44 @@
          -

          royalnetΒΆ

          +

          RoyalnetΒΆ

          Welcome to the documentation of Royalnet!

          diff --git a/docs/html/search.html b/docs/html/search.html index b6d883ae..979055c1 100644 --- a/docs/html/search.html +++ b/docs/html/search.html @@ -8,7 +8,7 @@ - Search — Royalnet 5.3.2 documentation + Search — Royalnet 5.4a2 documentation @@ -60,7 +60,7 @@
          - 5.3.2 + 5.4a2
          @@ -85,6 +85,7 @@ diff --git a/docs/html/searchindex.js b/docs/html/searchindex.js index 636b0035..3e8fcc87 100644 --- a/docs/html/searchindex.js +++ b/docs/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["apireference","index","randomdiscoveries"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["apireference.rst","index.rst","randomdiscoveries.rst"],objects:{"royalnet.alchemy":{Alchemy:[0,1,1,""],AlchemyException:[0,3,1,""],TableNotFoundError:[0,3,1,""],table_dfs:[0,4,1,""]},"royalnet.alchemy.Alchemy":{__init__:[0,2,1,""],get:[0,2,1,""],session_acm:[0,2,1,""],session_cm:[0,2,1,""]},"royalnet.bard":{YtdlDiscord:[0,1,1,""],YtdlFile:[0,1,1,""],YtdlInfo:[0,1,1,""],YtdlMp3:[0,1,1,""]},"royalnet.bard.YtdlDiscord":{convert_to_pcm:[0,2,1,""],delete_asap:[0,2,1,""],embed:[0,2,1,""],from_url:[0,2,1,""],info:[0,2,1,""],is_converted:[0,2,1,""],spawn_audiosource:[0,2,1,""]},"royalnet.bard.YtdlFile":{__init__:[0,2,1,""],aopen:[0,2,1,""],default_ytdl_args:[0,5,1,""],delete_asap:[0,2,1,""],download_file:[0,2,1,""],from_url:[0,2,1,""],has_info:[0,2,1,""],is_downloaded:[0,2,1,""],retrieve_info:[0,2,1,""],set_ytdlinfo_from_id3_tags:[0,2,1,""]},"royalnet.bard.YtdlInfo":{__init__:[0,2,1,""],from_url:[0,2,1,""]},"royalnet.bard.YtdlMp3":{convert_to_mp3:[0,2,1,""],delete_asap:[0,2,1,""],from_url:[0,2,1,""],info:[0,2,1,""],is_converted:[0,2,1,""]},"royalnet.commands":{Command:[0,1,1,""],CommandArgs:[0,1,1,""],CommandData:[0,1,1,""],CommandError:[0,3,1,""],CommandInterface:[0,1,1,""],ConfigurationError:[0,3,1,""],Event:[0,1,1,""],ExternalError:[0,3,1,""],InvalidInputError:[0,3,1,""],KeyboardKey:[0,1,1,""],ProgramError:[0,3,1,""],UnsupportedError:[0,3,1,""],UserError:[0,3,1,""]},"royalnet.commands.Command":{alchemy:[0,2,1,""],aliases:[0,5,1,""],config:[0,2,1,""],description:[0,5,1,""],loop:[0,2,1,""],name:[0,5,1,""],run:[0,2,1,""],serf:[0,2,1,""],syntax:[0,5,1,""]},"royalnet.commands.CommandArgs":{__getitem__:[0,2,1,""],joined:[0,2,1,""],match:[0,2,1,""],optional:[0,2,1,""]},"royalnet.commands.CommandData":{delete_invoking:[0,2,1,""],get_author:[0,2,1,""],keyboard:[0,2,1,""],reply:[0,2,1,""],session:[0,2,1,""],session_close:[0,2,1,""],session_commit:[0,2,1,""]},"royalnet.commands.CommandInterface":{alchemy:[0,2,1,""],call_herald_event:[0,2,1,""],config:[0,5,1,""],constellation:[0,5,1,""],loop:[0,2,1,""],name:[0,5,1,""],prefix:[0,5,1,""],serf:[0,5,1,""]},"royalnet.commands.Event":{"interface":[0,5,1,""],__init__:[0,2,1,""],alchemy:[0,2,1,""],config:[0,2,1,""],loop:[0,2,1,""],name:[0,5,1,""],run:[0,2,1,""],serf:[0,2,1,""]},"royalnet.commands.KeyboardKey":{press:[0,2,1,""]},"royalnet.constellation":{Constellation:[0,1,1,""],ExceptionStar:[0,1,1,""],PageStar:[0,1,1,""],Star:[0,1,1,""],shoot:[0,4,1,""]},"royalnet.constellation.Constellation":{Interface:[0,5,1,""],address:[0,5,1,""],alchemy:[0,5,1,""],events:[0,5,1,""],herald:[0,5,1,""],herald_task:[0,5,1,""],init_herald:[0,2,1,""],interface_factory:[0,2,1,""],loop:[0,5,1,""],network_handler:[0,2,1,""],port:[0,5,1,""],register_events:[0,2,1,""],register_exc_stars:[0,2,1,""],register_page_stars:[0,2,1,""],run_blocking:[0,2,1,""],run_process:[0,2,1,""],running:[0,5,1,""],starlette:[0,5,1,""]},"royalnet.constellation.ExceptionStar":{error:[0,5,1,""]},"royalnet.constellation.PageStar":{methods:[0,5,1,""],path:[0,5,1,""]},"royalnet.constellation.Star":{Session:[0,2,1,""],alchemy:[0,2,1,""],config:[0,2,1,""],constellation:[0,2,1,""],page:[0,2,1,""],session_acm:[0,2,1,""]},"royalnet.herald":{Broadcast:[0,1,1,""],Config:[0,1,1,""],ConnectionClosedError:[0,3,1,""],HeraldError:[0,3,1,""],InvalidServerResponseError:[0,3,1,""],Link:[0,1,1,""],LinkError:[0,3,1,""],Package:[0,1,1,""],Request:[0,1,1,""],Response:[0,1,1,""],ResponseFailure:[0,1,1,""],ResponseSuccess:[0,1,1,""],Server:[0,1,1,""],ServerError:[0,3,1,""]},"royalnet.herald.Broadcast":{from_dict:[0,2,1,""],to_dict:[0,2,1,""]},"royalnet.herald.Config":{copy:[0,2,1,""],from_config:[0,2,1,""],url:[0,2,1,""]},"royalnet.herald.Link":{broadcast:[0,2,1,""],connect:[0,2,1,""],identify:[0,2,1,""],receive:[0,2,1,""],request:[0,2,1,""],run:[0,2,1,""],send:[0,2,1,""]},"royalnet.herald.Package":{__init__:[0,2,1,""],from_dict:[0,2,1,""],from_json_bytes:[0,2,1,""],from_json_string:[0,2,1,""],reply:[0,2,1,""],to_dict:[0,2,1,""],to_json_bytes:[0,2,1,""],to_json_string:[0,2,1,""]},"royalnet.herald.Request":{from_dict:[0,2,1,""],to_dict:[0,2,1,""]},"royalnet.herald.Response":{from_dict:[0,2,1,""],to_dict:[0,2,1,""]},"royalnet.herald.Server":{find_client:[0,2,1,""],find_destination:[0,2,1,""],listener:[0,2,1,""],route_package:[0,2,1,""],run:[0,2,1,""],run_blocking:[0,2,1,""],serve:[0,2,1,""]},"royalnet.serf":{Serf:[0,1,1,""],SerfError:[0,3,1,""]},"royalnet.serf.Serf":{Interface:[0,5,1,""],alchemy:[0,5,1,""],call:[0,2,1,""],commands:[0,5,1,""],events:[0,5,1,""],herald:[0,5,1,""],herald_task:[0,5,1,""],identity_chain:[0,2,1,""],identity_table:[0,5,1,""],init_alchemy:[0,2,1,""],init_herald:[0,2,1,""],interface_factory:[0,2,1,""],interface_name:[0,5,1,""],loop:[0,5,1,""],master_table:[0,5,1,""],network_handler:[0,2,1,""],press:[0,2,1,""],register_commands:[0,2,1,""],register_events:[0,2,1,""],run:[0,2,1,""],run_process:[0,2,1,""]},"royalnet.utils":{FileAudioSource:[0,1,1,""],MultiLock:[0,1,1,""],andformat:[0,4,1,""],asyncify:[0,4,1,""],from_urluuid:[0,4,1,""],init_logging:[0,4,1,""],init_sentry:[0,4,1,""],numberemojiformat:[0,4,1,""],ordinalformat:[0,4,1,""],sentry_exc:[0,4,1,""],sleep_until:[0,4,1,""],to_urluuid:[0,4,1,""],underscorize:[0,4,1,""],ytdldateformat:[0,4,1,""]},"royalnet.utils.FileAudioSource":{__init__:[0,2,1,""],is_opus:[0,2,1,""],read:[0,2,1,""],stop:[0,2,1,""]},"royalnet.utils.MultiLock":{exclusive:[0,2,1,""],normal:[0,2,1,""]},royalnet:{alchemy:[0,0,0,"-"],backpack:[0,0,0,"-"],bard:[0,0,0,"-"],commands:[0,0,0,"-"],constellation:[0,0,0,"-"],herald:[0,0,0,"-"],serf:[0,0,0,"-"],utils:[0,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","exception","Python exception"],"4":["py","function","Python function"],"5":["py","attribute","Python attribute"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:exception","4":"py:function","5":"py:attribute"},terms:{"0th":0,"101st":0,"112th":0,"11th":0,"1st":0,"20m":0,"2nd":0,"abstract":0,"byte":0,"case":0,"class":0,"default":0,"final":0,"function":0,"ges\u00f9":0,"import":0,"int":0,"new":0,"null":0,"return":0,"short":0,"static":0,"true":0,"try":0,"while":[0,2],Has:0,Not:0,That:0,The:0,__getitem__:0,__init__:0,_gone:0,abl:0,about:0,abstracteventloop:0,access:0,accur:0,acquir:0,add:0,added:0,adding:0,addit:0,address:0,admin:0,aio:0,akin:0,alchemi:1,alchemy_cfg:0,alchemyexcept:0,aldent:0,alia:0,alias:0,all:0,allow:0,also:0,altern:0,amount:0,andformat:0,ani:0,anoth:0,anymor:0,anystr:0,aopen:0,api:1,app:0,arg:0,argument:0,around:0,async:0,asyncifi:0,asyncio:0,asyncron:0,audio:0,audiosourc:0,authent:2,author:0,autocomplet:0,autogener:0,avail:0,avoid:0,await:0,back:0,backpack:1,bard:1,base64:0,base:0,becaus:0,been:0,befor:0,being:0,between:0,bind:0,blockingli:0,bool:0,bot:[0,2],both:0,broadcast:0,bufferediobas:0,call:0,call_herald_ev:0,callabl:0,callback:0,can:0,cannot:0,carbonara:0,cate:0,central:0,chang:0,channel:0,charact:0,check:0,classmethod:0,close:0,code:[0,1],collect:0,command:1,commandarg:0,commanddata:0,commanderror:0,commandinterfac:0,commit:0,commun:0,complet:0,config:0,configur:0,configurationerror:0,connect:0,connectedcli:0,connectionclosederror:0,consol:0,constel:1,constellation_cfg:0,consum:0,contain:0,context:0,convers:0,convert:0,convert_to_mp3:0,convert_to_pcm:0,copi:0,coroutin:0,correspond:0,creat:0,current:0,dai:0,data:0,databas:0,database_uri:0,date:0,datetim:0,declar:0,default_ytdl_arg:0,delet:0,delete_asap:0,delete_invok:0,depth:0,descript:0,destin:0,destination_conv_id:0,detail:0,develop:2,dict:0,dictionari:0,did:0,differ:0,directli:0,discord:[0,1],discordserf:0,discoveri:1,displai:0,docstr:0,document:1,doe:0,doesn:0,don:0,down:0,download:0,download_fil:0,dure:0,each:0,easili:0,either:0,element:0,ellipsi:0,emb:0,emoji:0,empti:0,enabl:0,encod:0,encount:0,end:0,ending_t:0,engin:0,english:0,enough:0,epic:0,epoch:0,error:[0,1],error_if_non:0,error_if_unavail:0,even:0,event:0,event_nam:0,everi:0,exact:0,exampl:0,exc:0,exc_star:0,except:0,exceptionstar:0,exclus:0,execut:0,executor:0,exist:0,exit:0,explain:0,ext:0,extern:0,externalerror:0,extra_info:0,extract:0,extract_info:0,factor:0,fail:[0,2],fals:0,featur:0,fetch:0,ffmpeg:0,file:0,fileaudiosourc:0,filenam:0,find:0,find_client:0,find_destin:0,first:0,flag:0,form:0,format:0,found:[0,2],friendli:0,from:0,from_config:0,from_dict:0,from_json_byt:0,from_json_str:0,from_url:0,from_urluuid:0,gener:0,get:0,get_author:0,github:1,going:0,gone:0,group:0,handl:0,handler:0,happen:0,has:0,has_info:0,have:0,heartbeat:2,herald:1,herald_cfg:0,herald_task:0,heralderror:0,here:[0,2],how:0,http:0,ident:0,identifi:0,identity_chain:0,identity_t:0,ignoreerror:0,immediatli:0,implement:0,index:[0,1],info:0,inherit:0,init_alchemi:0,init_herald:0,init_log:0,init_sentri:0,initi:0,input:0,insid:0,instanc:0,instanti:0,instead:0,intend:0,interfac:0,interface_factori:0,interface_nam:0,internet:0,invalid:0,invalidinputerror:0,invalidserverresponseerror:0,invok:0,is_convert:0,is_download:0,is_opu:0,isn:0,item:0,iter:0,its:0,join:0,json:0,jsonabl:0,jsonrespons:0,kappa:0,keep:0,kei:0,keyboard:0,keyboardkei:0,keyword:0,kwarg:0,last:0,less:0,level:0,like:0,line:0,link:0,link_typ:0,linkerror:0,list:0,listen:0,lock:0,log:0,logging_cfg:0,loop:0,made:0,main:0,maintain:0,mallllco:0,manag:0,mani:0,markup:0,master:0,master_t:0,match:0,mean:0,memori:0,messag:0,method:0,middl:0,minimum:0,miss:0,modul:0,month:0,more:0,mp3:0,msg_type:0,multilock:0,multipl:0,multiprocess:0,must:0,name:0,network:0,network_handl:0,nid:0,no_warn:0,nobodi:0,node:0,non:0,none:0,noplaylist:0,normal:0,noth:0,notimpl:0,number:0,numberemojiformat:0,numer:0,object:0,onc:0,one:0,onli:0,open:0,option:0,optional_arg:0,opu:0,ordin:0,ordinal_numer:0,ordinalformat:0,org:0,origin:0,orm:0,other:0,out:2,outtmpl:0,overrid:0,pack:0,pack_cfg:0,packag:0,packet:0,packs_cfg:0,page:0,page_star:0,pagestar:0,paltri:0,paramet:0,part:0,particularli:0,pass:0,pasta:0,path:0,pattern:0,pcm:0,plai:0,pleas:0,port:0,possibl:0,post:0,prank:0,prefix:0,prepar:0,press:0,probabl:0,problem:0,procedur:0,process:0,program:0,programerror:0,properli:0,properti:0,proto:0,put:0,quiet:0,rais:0,random:[0,1],read:0,reason:2,receiv:0,reciev:0,recreat:0,refer:1,regex:0,regist:0,register_command:0,register_ev:0,register_exc_star:0,register_page_star:0,relat:0,relationship:0,remot:0,render:0,replac:0,repli:0,repres:0,represent:0,request:0,request_handl:0,requir:0,require_at_least:0,required_arg:0,resourc:0,respons:0,responsefailur:0,responsesuccess:0,rest:0,result:0,retriev:0,retrieve_for_url:0,retrieve_info:0,rout:0,route_packag:0,row:0,royalnet:0,run:0,run_block:0,run_process:0,safe:0,schema:0,search:0,second:0,secret:0,section:0,secur:0,select:0,self:0,send:0,sensei:0,sent:0,sentry_cfg:0,sentry_exc:0,separ:0,sequenc:0,serf:1,serferror:0,serv:0,server:0,servererror:0,session:0,session_acm:0,session_clos:0,session_cm:0,session_commit:0,set:0,set_ytdlinfo_from_id3_tag:0,shoot:0,shortcut:0,should:0,shouldn:0,simultan:0,singl:0,sleep:0,sleep_until:0,small:0,some:[0,2],someth:0,somewher:0,soon:0,sourc:0,source_conv_id:0,space:0,spaggia:0,spaghetti:0,spawn_audiosourc:0,specif:0,specifi:0,sphinx:0,sql:0,sqlalchemi:0,stai:0,standard:0,star:0,starlett:0,start:0,starting_t:0,statement:0,steffo:0,stop:[0,2],str:0,stream:0,string:0,stuff:0,subclass:0,submodul:0,success:0,support:0,syntax:0,tabl:0,table_df:0,tablenotfounderror:0,target:0,task:0,telegram:0,telegramserf:0,text:0,than:0,thei:0,them:0,thi:0,thing:[0,2],thread:0,through:0,time:[0,2],titl:0,to_dict:0,to_json_byt:0,to_json_str:0,to_urluuid:0,too:0,trigger:0,tupl:0,two:0,type:0,underscor:0,undescrib:0,undocu:1,unexpectedli:0,unicod:0,union:0,unsupportederror:0,until:0,uri:0,url:0,use:0,used:0,useful:0,user:0,usererror:0,uses:0,using:0,usual:0,utf:0,util:1,uuid:0,uvicorn:0,valu:0,valueerror:0,video:0,viktya:0,want:0,web:0,webpag:0,webserv:0,websit:0,websocket:[0,1],websocketserverprotocol:0,weird:0,welcom:1,went:0,were:[0,2],what:0,when:0,where:0,which:0,wiki:0,wikipedia:0,won:0,word:0,work:0,wrapper:0,wrong:0,wrong_____:0,year:0,yet:0,you:0,youtube_dl:0,youtubedl:0,ytdl_arg:0,ytdl_file:0,ytdldateformat:0,ytdldiscord:0,ytdlfile:0,ytdlinfo:0,ytdlmp3:0,yyyi:0,yyyymmdd:0},titles:["API Reference","royalnet","Random discoveries"],titleterms:{alchemi:0,api:0,backpack:0,bard:0,code:2,command:0,constel:0,discord:2,discoveri:2,error:2,herald:0,link:1,random:2,refer:0,royalnet:1,serf:0,some:1,undocu:2,useful:1,util:0,websocket:2}}) \ No newline at end of file +Search.setIndex({docnames:["apireference","index","packs/command","packs/event","packs/pack","packs/star","packs/table","randomdiscoveries"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["apireference.rst","index.rst","packs\\command.rst","packs\\event.rst","packs\\pack.rst","packs\\star.rst","packs\\table.rst","randomdiscoveries.rst"],objects:{"royalnet.alchemy":{Alchemy:[0,1,1,""],AlchemyException:[0,3,1,""],TableNotFoundError:[0,3,1,""],table_dfs:[0,4,1,""]},"royalnet.alchemy.Alchemy":{__init__:[0,2,1,""],get:[0,2,1,""],session_acm:[0,2,1,""],session_cm:[0,2,1,""]},"royalnet.bard":{BardError:[0,3,1,""],MultipleFilesError:[0,3,1,""],NotFoundError:[0,3,1,""],YtdlError:[0,3,1,""],YtdlFile:[0,1,1,""],YtdlInfo:[0,1,1,""]},"royalnet.bard.YtdlFile":{__init__:[0,2,1,""],aopen:[0,2,1,""],default_ytdl_args:[0,5,1,""],delete_asap:[0,2,1,""],download_file:[0,2,1,""],from_url:[0,2,1,""],has_info:[0,2,1,""],is_downloaded:[0,2,1,""],retrieve_info:[0,2,1,""],set_ytdlinfo_from_id3_tags:[0,2,1,""]},"royalnet.bard.YtdlInfo":{__init__:[0,2,1,""],from_url:[0,2,1,""]},"royalnet.commands":{Command:[0,1,1,""],CommandArgs:[0,1,1,""],CommandData:[0,1,1,""],CommandError:[0,3,1,""],CommandInterface:[0,1,1,""],ConfigurationError:[0,3,1,""],Event:[0,1,1,""],ExternalError:[0,3,1,""],InvalidInputError:[0,3,1,""],KeyboardKey:[0,1,1,""],ProgramError:[0,3,1,""],UnsupportedError:[0,3,1,""],UserError:[0,3,1,""]},"royalnet.commands.Command":{alchemy:[0,2,1,""],aliases:[0,5,1,""],config:[0,2,1,""],description:[0,5,1,""],loop:[0,2,1,""],name:[0,5,1,""],run:[0,2,1,""],serf:[0,2,1,""],syntax:[0,5,1,""]},"royalnet.commands.CommandArgs":{__getitem__:[0,2,1,""],joined:[0,2,1,""],match:[0,2,1,""],optional:[0,2,1,""]},"royalnet.commands.CommandData":{delete_invoking:[0,2,1,""],get_author:[0,2,1,""],keyboard:[0,2,1,""],reply:[0,2,1,""],session:[0,2,1,""],session_close:[0,2,1,""],session_commit:[0,2,1,""]},"royalnet.commands.CommandInterface":{alchemy:[0,2,1,""],call_herald_event:[0,2,1,""],config:[0,5,1,""],constellation:[0,5,1,""],loop:[0,2,1,""],name:[0,5,1,""],prefix:[0,5,1,""],serf:[0,5,1,""],table:[0,2,1,""]},"royalnet.commands.Event":{"interface":[0,5,1,""],__init__:[0,2,1,""],alchemy:[0,2,1,""],config:[0,2,1,""],loop:[0,2,1,""],name:[0,5,1,""],run:[0,2,1,""],serf:[0,2,1,""]},"royalnet.commands.KeyboardKey":{press:[0,2,1,""]},"royalnet.constellation":{Constellation:[0,1,1,""],ExceptionStar:[0,1,1,""],PageStar:[0,1,1,""],Star:[0,1,1,""],shoot:[0,4,1,""]},"royalnet.constellation.Constellation":{Interface:[0,5,1,""],address:[0,5,1,""],alchemy:[0,5,1,""],events:[0,5,1,""],herald:[0,5,1,""],herald_task:[0,5,1,""],init_herald:[0,2,1,""],interface_factory:[0,2,1,""],loop:[0,5,1,""],network_handler:[0,2,1,""],port:[0,5,1,""],register_events:[0,2,1,""],register_exc_stars:[0,2,1,""],register_page_stars:[0,2,1,""],run_blocking:[0,2,1,""],run_process:[0,2,1,""],running:[0,5,1,""],starlette:[0,5,1,""]},"royalnet.constellation.ExceptionStar":{error:[0,5,1,""]},"royalnet.constellation.PageStar":{methods:[0,5,1,""],path:[0,5,1,""]},"royalnet.constellation.Star":{Session:[0,2,1,""],alchemy:[0,2,1,""],config:[0,2,1,""],constellation:[0,2,1,""],page:[0,2,1,""],session_acm:[0,2,1,""]},"royalnet.herald":{Broadcast:[0,1,1,""],Config:[0,1,1,""],ConnectionClosedError:[0,3,1,""],HeraldError:[0,3,1,""],InvalidServerResponseError:[0,3,1,""],Link:[0,1,1,""],LinkError:[0,3,1,""],Package:[0,1,1,""],Request:[0,1,1,""],Response:[0,1,1,""],ResponseFailure:[0,1,1,""],ResponseSuccess:[0,1,1,""],Server:[0,1,1,""],ServerError:[0,3,1,""]},"royalnet.herald.Broadcast":{from_dict:[0,2,1,""],to_dict:[0,2,1,""]},"royalnet.herald.Config":{copy:[0,2,1,""],from_config:[0,2,1,""],url:[0,2,1,""]},"royalnet.herald.Link":{broadcast:[0,2,1,""],connect:[0,2,1,""],identify:[0,2,1,""],receive:[0,2,1,""],request:[0,2,1,""],run:[0,2,1,""],send:[0,2,1,""]},"royalnet.herald.Package":{__init__:[0,2,1,""],from_dict:[0,2,1,""],from_json_bytes:[0,2,1,""],from_json_string:[0,2,1,""],reply:[0,2,1,""],to_dict:[0,2,1,""],to_json_bytes:[0,2,1,""],to_json_string:[0,2,1,""]},"royalnet.herald.Request":{from_dict:[0,2,1,""],to_dict:[0,2,1,""]},"royalnet.herald.Response":{from_dict:[0,2,1,""],to_dict:[0,2,1,""]},"royalnet.herald.Server":{find_client:[0,2,1,""],find_destination:[0,2,1,""],listener:[0,2,1,""],route_package:[0,2,1,""],run:[0,2,1,""],run_blocking:[0,2,1,""],serve:[0,2,1,""]},"royalnet.serf":{Serf:[0,1,1,""],SerfError:[0,3,1,""]},"royalnet.serf.Serf":{Interface:[0,5,1,""],alchemy:[0,5,1,""],call:[0,2,1,""],commands:[0,5,1,""],events:[0,5,1,""],herald:[0,5,1,""],herald_task:[0,5,1,""],identity_chain:[0,2,1,""],identity_table:[0,5,1,""],init_alchemy:[0,2,1,""],init_herald:[0,2,1,""],interface_factory:[0,2,1,""],interface_name:[0,5,1,""],loop:[0,5,1,""],master_table:[0,5,1,""],network_handler:[0,2,1,""],press:[0,2,1,""],register_commands:[0,2,1,""],register_events:[0,2,1,""],run:[0,2,1,""],run_process:[0,2,1,""]},"royalnet.utils":{MultiLock:[0,1,1,""],andformat:[0,4,1,""],asyncify:[0,4,1,""],from_urluuid:[0,4,1,""],init_logging:[0,4,1,""],init_sentry:[0,4,1,""],numberemojiformat:[0,4,1,""],ordinalformat:[0,4,1,""],sentry_exc:[0,4,1,""],sleep_until:[0,4,1,""],to_urluuid:[0,4,1,""],underscorize:[0,4,1,""],ytdldateformat:[0,4,1,""]},"royalnet.utils.MultiLock":{exclusive:[0,2,1,""],normal:[0,2,1,""]},royalnet:{alchemy:[0,0,0,"-"],backpack:[0,0,0,"-"],bard:[0,0,0,"-"],commands:[0,0,0,"-"],constellation:[0,0,0,"-"],herald:[0,0,0,"-"],serf:[0,0,0,"-"],utils:[0,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","exception","Python exception"],"4":["py","function","Python function"],"5":["py","attribute","Python attribute"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:exception","4":"py:function","5":"py:attribute"},terms:{"0th":0,"101st":0,"112th":0,"11th":0,"1st":0,"2nd":0,"abstract":0,"byte":0,"case":[0,2],"class":[0,2],"default":[0,2],"final":0,"function":[0,2],"ges\u00f9":0,"import":[0,2],"int":0,"new":[0,1,4],"null":0,"return":[0,2],"short":[0,2],"static":0,"super":2,"true":[0,2],"try":[0,2],"while":[0,2,7],Adding:[1,4],And:2,For:2,Has:0,Not:[0,2],That:0,The:[0,2],Then:2,There:2,These:2,Use:2,Using:[1,4],__getitem__:0,__init__:[0,2],_gone:0,abl:0,about:[0,2],abstracteventloop:0,access:[0,1,4],accur:0,acquir:0,add:0,added:[0,2],adding:[0,2],addit:0,addition:2,address:0,admin:0,aio:0,akin:0,alchemi:[1,4],alchemy_cfg:0,alchemy_easi:0,alchemy_hard:0,alchemyexcept:0,aldent:0,alia:0,alias:0,all:[0,2],allow:[0,2],also:0,altern:0,alwai:2,amount:0,andformat:0,ani:[0,2],anoth:0,anymor:0,anystr:0,aopen:0,api:[1,2],app:0,append:2,arg:[0,2],argument:[0,1,4],around:0,ascend:2,assertionerror:2,async:[0,2],asyncifi:[0,2],asyncio:[0,2],asyncron:[0,2],attribut:2,authent:7,author:0,autocomplet:0,autogener:0,avail:[0,2],avoid:[0,2],await:[0,2],back:2,backpack:[1,2],banana:2,bard:1,barderror:0,base64:0,base:0,becaus:[0,2],been:0,befor:[0,2],begin:2,being:[0,2],between:0,bind:0,blockingli:0,bool:0,bot:[0,1,4,7],both:0,brace:2,bracket:2,brief:2,broadcast:0,bug:2,call:[0,1,4],call_herald_ev:0,callabl:0,callback:0,can:[0,2],cannot:0,carb:2,carbonara:[0,2],cate:0,caus:[0,2],central:0,certain:2,chang:0,channel:0,charact:0,chat:2,check:0,classmethod:0,close:[0,2],code:[0,1,4],collect:0,column:2,come:2,command:[1,4],commandarg:[0,2],commanddata:[0,2],commanderror:[0,2],commandinterfac:0,commit:0,commun:0,comparis:2,complet:0,complex:2,config:[0,2],configur:0,configurationerror:[0,2],connect:[0,2],connectedcli:0,connectionclosederror:0,consid:2,consol:0,constel:1,constellation_cfg:0,contain:[0,2],context:0,convers:0,convert:0,copi:0,coroutin:[0,1,4],correspond:0,could:2,creat:[0,1],curli:2,current:0,dai:0,data:[0,2],databas:[0,1,4],database_uri:0,date:0,datetim:0,debug:2,declar:0,def:2,default_ytdl_arg:0,delet:[0,1,4],delete_asap:0,delete_invok:[0,2],dent:2,depend:2,depth:0,desc:2,descend:2,descript:[0,2],destin:0,destination_conv_id:0,detail:0,develop:7,dict:0,dictionari:0,did:[0,2],differ:[0,2],direct:[1,4],directli:[0,2],discord:[0,1],discordserf:0,discoveri:1,displai:[0,1,4],docstr:0,document:[1,2,3,4,5,6],doe:[0,2],doesn:0,don:[0,2],done:2,down:0,download:0,download_1_terabyte_of_spaghetti:2,download_fil:0,dure:0,each:0,easili:0,effect:2,either:0,element:[0,2],ellipsi:0,els:2,emoji:[0,2],enabl:0,encod:0,encount:0,end:0,ending_t:0,engin:[0,2],english:0,enough:[0,2],epic:0,epoch:0,equival:2,error:[0,1,4],error_if_non:0,error_if_unavail:[0,2],establish:2,even:0,event:[0,1,4],event_nam:0,everi:[0,2],exact:[0,2],exampl:[0,2],exc:0,exc_star:0,except:[0,2],exceptionstar:0,exclus:0,execut:[0,2],executor:0,exist:[0,2],exit:0,expect:2,explain:0,express:[1,4],ext:0,extern:[0,2],externalerror:[0,2],extra:0,extra_info:0,extract:0,extract_info:0,eyed3:0,facilit:2,factor:0,fail:[0,7],fals:0,featur:0,fetch:[0,1,4],ffmpeg_python:0,field:2,file:[0,2],filenam:0,filter:[1,4],find:0,find_client:0,find_destin:0,finish:2,first:[0,2],flag:0,form:0,format:0,found:[0,2,7],friendli:0,from:[0,2],from_config:0,from_dict:0,from_json_byt:0,from_json_str:0,from_url:0,from_urluuid:0,full:[1,4],gener:0,get:[0,2],get_author:0,github:1,going:0,gone:0,greater:2,group:[0,2],handl:0,handler:0,happen:0,has:[0,2],has_info:0,have:[0,2],heartbeat:7,herald:1,herald_cfg:0,herald_task:0,heralderror:0,here:[2,7],how:0,http:0,ident:0,identifi:0,identity_chain:0,identity_t:0,ignor:2,ignoreerror:0,imag:2,implement:0,includ:2,index:[0,1],info:0,inherit:[0,2],init_alchemi:0,init_herald:0,init_log:0,init_sentri:0,initi:[0,1,4],input:0,insid:[0,2],instal:0,instanc:[0,2],instanti:0,instead:[0,2],instruct:0,intend:0,interact:2,interfac:[0,2],interface_factori:0,interface_nam:0,internet:0,invalid:[0,2],invalidinputerror:[0,2],invalidserverresponseerror:0,invok:[0,1,4],is_download:0,is_open:2,isn:[0,2],itali:2,item:0,iter:0,its:0,job:[1,4],join:[0,2],json:0,jsonabl:0,jsonrespons:0,just:2,kappa:0,keep:2,kei:0,keyboard:[0,1,4],keyboardkei:0,keyword:[0,2],kind:2,kitchen:2,kwarg:0,last:0,later:2,less:0,level:0,like:[0,2],line:0,link:0,link_typ:0,linkerror:0,list:[0,2],listen:0,lock:0,log:0,logging_cfg:0,look:2,loop:0,made:0,mai:2,main:0,maintain:0,make:2,mallllco:0,manag:0,mani:0,markup:0,master:0,master_t:0,match:[0,2],mean:[0,2],meantim:2,media:0,member:2,mention:2,messag:[0,1,4],metadata:2,method:[0,2],middl:0,minimum:[0,2],misconfigur:2,miss:[0,2],mistak:2,modul:0,month:0,more:[0,1,4],msg_type:0,multilock:0,multipl:[0,2],multiplefileserror:0,multiprocess:0,music:0,must:0,name:[0,2],network:0,network_handl:0,next:2,nid:0,no_warn:0,nobodi:0,node:0,non:0,none:[0,2],noplaylist:0,normal:[0,2],notfounderror:0,noth:[0,2],notic:2,notimpl:0,now:2,number:[0,2],numberemojiformat:0,numer:0,object:[0,2],onc:[0,2],one:[0,2],one_or_non:2,ones:2,onli:[0,2],open:0,oper:[1,4],option:[0,1,4],optional_arg:0,order:[1,4],order_bi:2,ordin:0,ordinal_numer:0,ordinalformat:0,org:0,origin:0,orm:[0,2],other:[0,2],otherwis:2,out:7,outtmpl:0,over:0,overrid:[0,2],pack:[0,1],pack_cfg:0,packag:0,packet:0,packs_cfg:0,page:0,page_star:0,pagestar:0,paltri:0,paramet:[0,2],part:[0,2],particularli:0,pass:[0,2],pasta:0,path:0,pattern:[0,2],ping:2,pingcommand:2,pip:0,plai:2,pleas:0,pong:2,port:0,possibl:[0,2],post:0,postgresql:2,prank:0,prefix:0,prepar:0,present:2,press:0,previou:2,previous:2,probabl:0,problem:[0,2],procedur:0,process:0,program:[0,2],programerror:[0,2],project:0,properli:0,properti:0,proto:0,provid:0,psycopg2:0,put:0,pypi:0,python:2,queri:[1,4],quiet:0,rais:[0,1,4],random:[0,1],rbt:2,read:[0,2],readi:2,reason:7,receiv:[0,2],reciev:0,recogn:2,recreat:0,refer:1,regex:0,regist:0,register_command:0,register_ev:0,register_exc_star:0,register_page_star:0,regular:[1,4],relat:0,relationship:0,rememb:2,remot:0,render:0,repeat:[1,4],replac:0,repli:[0,2],report:0,repres:0,represent:0,request:[0,2],request_handl:0,requestedpasta:2,requir:[0,2],require_at_least:[0,2],required_arg:0,resourc:0,respect:2,respons:0,responsefailur:0,responsesuccess:0,rest:[0,2],result:[0,1,4],retriev:0,retrieve_for_url:0,retrieve_info:0,right_now:2,role:2,rout:0,route_packag:0,row:[0,2],royalnet:[0,2],run:[0,1,4],run_block:0,run_process:0,safe:0,same:2,schema:0,script:2,search:0,second:0,secret:0,section:[0,2,3,4,5,6],secur:0,select:0,self:[0,2],send:[0,2],sensei:0,sent:[0,2],sentri:0,sentry_cfg:0,sentry_exc:0,separ:[0,2],sequenc:0,serf:1,serferror:0,serv:0,server:0,servererror:0,session:[0,2],session_acm:0,session_clos:0,session_cm:0,session_commit:0,set:[0,2],set_ytdlinfo_from_id3_tag:0,shoot:0,shortcut:0,should:[0,2],shouldn:0,side:2,simpl:2,simultan:0,singl:[0,2],sleep:[0,2],sleep_until:0,slow:[1,4],small:[0,2],some:[0,2,7],someth:[0,2],somewher:0,soon:0,sort:2,sourc:0,source_conv_id:0,space:[0,2],spaggia:0,spaghetti:[0,2],spaghetticommand:2,special:2,specif:[0,2],specifi:[0,2],sphinx:0,sql:0,sqlalchemi:[0,2],squar:2,stai:[0,2],standard:0,star:[0,1,4],starlett:0,start:[0,2],starting_t:0,statement:0,steffo:0,stop:[2,7],str:[0,2],string:[0,1,4],stuff:0,subclass:[0,2],submodul:0,subpackag:0,success:0,support:[0,2],syntax:[0,2],tabl:[0,1,2,4],table_df:0,tablenotfounderror:0,take:2,target:0,task:0,telegram:[0,2],telegramserf:0,tell:2,text:0,than:[0,2],thei:[0,2],them:[0,2],thi:[0,2,3,4,5,6],thing:[2,7],think:2,thought:2,thread:0,through:[0,2],time:[0,2,7],titl:0,to_dict:0,to_json_byt:0,to_json_str:0,to_urluuid:0,toml:2,too:[0,2],tri:2,trigger:0,tupl:[0,2],two:0,type:[0,2],unavail:2,underscor:0,undescrib:0,undocu:1,unexpectedli:0,unicod:0,union:[0,2],unsupportederror:[0,2],until:[0,2],uri:0,url:0,use:[0,2],used:[0,2],useful:0,user:[0,2],usererror:[0,2],usernam:2,uses:0,using:[0,2],usual:0,utf:0,util:[1,2],uuid:0,uvicorn:0,valu:[0,2],valueerror:0,video:0,viktya:0,wai:2,want:[0,2],wasn:[0,2],web:0,webpag:0,webserv:0,websit:[0,2],websocket:[0,1],websocketserverprotocol:0,weird:0,welcom:1,went:0,were:[0,2,7],what:[0,2],when:[0,2],whenev:2,where:0,which:[0,2],wiki:0,wikipedia:0,won:[0,2],word:0,work:[0,2],wrap:2,wrapper:0,wrong:[0,2],wrong_____:0,year:0,yet:[0,2,3,4,5,6],you:[0,2],your:2,youtube_dl:0,youtubedl:0,ytdl_arg:0,ytdldateformat:0,ytdlerror:0,ytdlfile:0,ytdlinfo:0,yyyi:0,yyyymmdd:0},titles:["API Reference","Royalnet","Adding a Command to the Pack","Using Events","Creating a Royalnet Pack","Adding a Star to the Pack","Using Tables and databases","Random discoveries"],titleterms:{"new":2,Adding:[2,5],Using:[2,3,6],access:2,alchemi:[0,2],api:0,argument:2,backpack:0,bard:0,bot:2,call:2,code:[2,7],command:[0,2],constel:0,coroutin:2,creat:[2,4],databas:[2,6],delet:2,direct:2,discord:7,discoveri:7,displai:2,error:[2,7],event:[2,3],express:2,fetch:2,filter:2,full:2,herald:0,initi:2,invok:2,job:2,keyboard:2,link:1,messag:2,more:2,oper:2,option:2,order:2,pack:[2,4,5],queri:2,rais:2,random:7,refer:0,regular:2,repeat:2,result:2,royalnet:[1,4],run:2,serf:0,slow:2,some:1,star:5,string:2,tabl:6,undocu:7,useful:1,util:0,websocket:7}}) \ No newline at end of file diff --git a/docs_source/index.rst b/docs_source/index.rst index e2605ae8..1e5a928f 100644 --- a/docs_source/index.rst +++ b/docs_source/index.rst @@ -1,4 +1,4 @@ -royalnet +Royalnet ==================================== Welcome to the documentation of Royalnet! @@ -6,6 +6,7 @@ Welcome to the documentation of Royalnet! .. toctree:: :maxdepth: 5 + packs/pack randomdiscoveries apireference diff --git a/docs_source/packs/command.rst b/docs_source/packs/command.rst new file mode 100644 index 00000000..652c57be --- /dev/null +++ b/docs_source/packs/command.rst @@ -0,0 +1,350 @@ +.. currentmodule:: royalnet.commands + +Adding a Command to the Pack +==================================== + +A Royalnet Command is a small script that is run whenever a specific message is sent to a Royalnet interface. + +A Command code looks like this: :: + + import royalnet.commands as rc + + class PingCommand(rc.Command): + name = "ping" + + description = "Play ping-pong with the bot." + + def __init__(self, interface): + # This code is run just once, while the bot is starting + super().__init__() + + async def run(self, args: rc.CommandArgs, data: rc.CommandData): + # This code is run every time the command is called + await data.reply("Pong!") + +Creating a new Command +------------------------------------ + +First, think of a ``name`` for your command. +It's the name your command will be called with: for example, the "spaghetti" command will be called by typing **/spaghetti** in chat. +Try to keep the name as short as possible, while staying specific enough so no other command will have the same name. + +Next, create a new Python file with the ``name`` you have thought of. +The previously mentioned "spaghetti" command should have a file called ``spaghetti.py``. + +Then, in the first row of the file, import the :class:`Command` class from royalnet, and create a new class inheriting from it: :: + + import royalnet.commands as rc + + class SpaghettiCommand(rc.Command): + ... + +Inside the class, override the attributes ``name`` and ``description`` with respectively the **name of the command** and a **small description of what the command will do**: :: + + import royalnet.commands as rc + + class SpaghettiCommand(rc.Command): + name = "spaghetti" + + description = "Send a spaghetti emoji in the chat." + +Now override the :meth:`Command.run` method, adding the code you want the bot to run when the command is called. + +To send a message in the chat the command was called in, you can use the :meth:`CommandData.reply` coroutine: :: + + import royalnet.commands as rc + + class SpaghettiCommand(rc.Command): + name = "spaghetti" + + description = "Send a spaghetti emoji in the chat." + + async def run(self, args: rc.CommandArgs, data: rc.CommandData): + await data.reply("🍝") + +And... it's done! The command is ready to be :doc:`added to your pack `! + +Command arguments +------------------------------------ + +A command can have some arguments passed by the user: for example, on Telegram an user may type `/spaghetti carbonara al-dente` +to pass the :class:`str` `"carbonara al-dente"` to the command code. + +These arguments can be accessed in multiple ways through the ``args`` parameter passed to the :meth:`Command.run` +method. + +If you want your command to use arguments, override the ``syntax`` class attribute with a brief description of the +syntax of your command, possibly using {curly braces} for required arguments and [square brackets] for optional +ones. :: + + import royalnet.commands as rc + + class SpaghettiCommand(rc.Command): + name = "spaghetti" + + description = "Send a spaghetti emoji in the chat." + + syntax = "(requestedpasta)" + + async def run(self, args: rc.CommandArgs, data: rc.CommandData): + await data.reply(f"🍝 Here's your {args[0]}!") + + +Direct access +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can consider arguments as if they were separated by spaces. + +You can then access command arguments directly by number as if the args object was a list of :class:`str`. + +If you request an argument with a certain number, but the argument does not exist, an +:exc:`.InvalidInputError` is raised, making the arguments accessed in this way **required**. :: + + args[0] + # "carbonara" + + args[1] + # "al-dente" + + args[2] + # raises InvalidInputError + +Optional access +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you don't want arguments to be required, you can access them through the :meth:`CommandArgs.optional` method: it +will return ``None`` if the argument wasn't passed, making it **optional**. :: + + args.optional(0) + # "carbonara" + + args.optional(1) + # "al-dente" + + args.optional(2) + # None + +You can specify a default result too, so that the method will return it instead of returning ``None``: :: + + args.optional(2, default="banana") + # "banana" + +Full string +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want the full argument string, you can use the :meth:`CommandArgs.joined` method. :: + + args.joined() + # "carbonara al-dente" + +You can specify a minimum number of arguments too, so that an :exc:`.InvalidInputError` will be +raised if not enough arguments are present: :: + + args.joined(require_at_least=3) + # raises InvalidInputError + +Regular expressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For more complex commands, you may want to get arguments through `regular expressions `_. + +You can then use the :meth:`CommandArgs.match` method, which tries to match a pattern to the command argument string, +which returns a tuple of the matched groups and raises an :exc:`.InvalidInputError` if there is no match. + +To match a pattern, :func:`re.match` is used, meaning that Python will try to match only at the beginning of the string. :: + + args.match(r"(carb\w+)") + # ("carbonara",) + + args.match(r"(al-\w+)") + # raises InvalidInputError + + args.match(r"\s*(al-\w+)") + # ("al-dente",) + + args.match(r"\s*(carb\w+)\s*(al-\w+)") + # ("carbonara", "al-dente") + +Raising errors +--------------------------------------------- + +If you want to display an error message to the user, you can raise a :exc:`.CommandError` using the error message as argument: :: + + if not kitchen.is_open(): + raise CommandError("The kitchen is closed. Come back later!") + +There are some subclasses of :exc:`.CommandError` that can be used for some more specific cases: + +:exc:`.UserError` + The user did something wrong, it is not a problem with the bot. + +:exc:`.InvalidInputError` + The arguments the user passed to the command by the user are invalid. + Displays the command syntax in the error message. + +:exc:`.UnsupportedError` + The command is not supported on the interface it is being called. + +:exc:`.ConfigurationError` + The ``config.toml`` file was misconfigured (a value is missing or invalid). + +:exc:`.ExternalError` + An external API the command depends on is unavailable or returned an error. + +:exc:`.ProgramError` + An error caused by a programming mistake. Equivalent to :exc:`AssertionError`, but includes a message to facilitate debugging. + +Coroutines and slow operations +------------------------------------ + +You may have noticed that in the previous examples we used ``await data.reply("🍝")`` instead of just ``data.reply("🍝")``. + +This is because :meth:`CommandData.reply` isn't a simple method: it is a coroutine, a special kind of function that +can be executed separately from the rest of the code, allowing the bot to do other things in the meantime. + +By adding the ``await`` keyword before the ``data.reply("🍝")``, we tell the bot that it can do other things, like +receiving new messages, while the message is being sent. + +You should avoid running slow normal functions inside bot commands, as they will stop the bot from working until they +are finished and may cause bugs in other parts of the code! :: + + async def run(self, args, data): + # Don't do this! + image = download_1_terabyte_of_spaghetti("right_now", from="italy") + ... + +If the slow function you want does not cause any side effect, you can wrap it with the :func:`royalnet.utils.asyncify` +function: :: + + async def run(self, args, data): + # If the called function has no side effect, you can do this! + image = await asyncify(download_1_terabyte_of_spaghetti, "right_now", from="italy") + ... + +Avoid using :func:`time.sleep` function, as it is considered a slow operation: use instead :func:`asyncio.sleep`, +a coroutine that does the same exact thing but in an asyncronous way. + +Delete the invoking message +------------------------------------ + +The invoking message of a command is the message that the user sent that the bot recognized as a command; for example, +the message ``/spaghetti carbonara`` is the invoking message for the ``spaghetti`` command run. + +You can have the bot delete the invoking message for a command by calling the :class:`CommandData.delete_invoking` +method: :: + + async def run(self, args, data): + await data.delete_invoking() + +Not all interfaces support deleting messages; by default, if the interface does not support deletions, the call is +ignored. + +You can have the method raise an error if the message can't be deleted by setting the ``error_if_unavailable`` parameter +to True: :: + + async def run(self, args, data): + try: + await data.delete_invoking(error_if_unavailable=True) + except royalnet.error.UnsupportedError: + await data.reply("🚫 The message could not be deleted.") + else: + await data.reply("βœ… The message was deleted!") + +Using the database +------------------------------------ + +Bots can be connected to a PostgreSQL database through a special SQLAlchemy interface called +:class:`royalnet.alchemy.Alchemy`. + +If the connection is established, the ``self.alchemy`` and ``data.session`` fields will be +available for use in commands. + +``self.alchemy`` is an instance of :class:`royalnet.alchemy.Alchemy`, which contains the +:class:`sqlalchemy.engine.Engine`, metadata and tables, while ``data.session`` is a +:class:`sqlalchemy.orm.session.Session`, and can be interacted in the same way as one. + +Querying the database +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can :class:`sqlalchemy.orm.query.Query` the database using the SQLAlchemy ORM. + +The SQLAlchemy tables can be found inside :class:`royalnet.alchemy.Alchemy` with the :meth:`royalnet.alchemy.Alchemy.get` method: :: + + import royalnet.backpack.tables as rbt + User = self.alchemy.get(rbt.User) + +Adding filters to the query +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can filter the query results with the :meth:`sqlalchemy.orm.query.Query.filter` method. + +.. note:: Remember to always use a table column as first comparision element, as it won't work otherwise. + +:: + + query = query.filter(User.role == "Member") + + +Ordering the results of a query +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can order the query results in **ascending order** with the :meth:`sqlalchemy.orm.query.Query.order_by` method. :: + + query = query.order_by(User.username) + +Additionally, you can append the `.desc()` method to a table column to sort in **descending order**: :: + + query = query.order_by(User.username.desc()) + +Fetching the results of a query +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can fetch the query results with the :meth:`sqlalchemy.orm.query.Query.all`, +:meth:`sqlalchemy.orm.query.Query.first`, :meth:`sqlalchemy.orm.query.Query.one` and +:meth:`sqlalchemy.orm.query.Query.one_or_none` methods. + +Remember to use :func:`royalnet.utils.asyncify` when fetching results, as it may take a while! + +Use :meth:`sqlalchemy.orm.query.Query.all` if you want a :class:`list` of **all results**: :: + + results: list = await asyncify(query.all) + +Use :meth:`sqlalchemy.orm.query.Query.first` if you want **the first result** of the list, or ``None`` if +there are no results: :: + + result: typing.Union[..., None] = await asyncify(query.first) + +Use :meth:`sqlalchemy.orm.query.Query.one` if you expect to have **a single result**, and you want the command to +raise an error if any different number of results is returned: :: + + result: ... = await asyncify(query.one) # Raises an error if there are no results or more than a result. + +Use :meth:`sqlalchemy.orm.query.Query.one_or_none` if you expect to have **a single result**, or **nothing**, and +if you want the command to raise an error if the number of results is greater than one. :: + + result: typing.Union[..., None] = await asyncify(query.one_or_none) # Raises an error if there is more than a result. + +More Alchemy +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can read more about :mod:`sqlalchemy` at their `website `_. + +Calling Events +------------------------------------ + +This section is not documented yet. + +Displaying Keyboards +------------------------------------ + +This section is not documented yet. + +Running code at the initialization of the bot +--------------------------------------------- + +This section is not documented yet. + +Running repeating jobs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This section is not documented yet. \ No newline at end of file diff --git a/docs_source/packs/event.rst b/docs_source/packs/event.rst new file mode 100644 index 00000000..9b4f80a9 --- /dev/null +++ b/docs_source/packs/event.rst @@ -0,0 +1,4 @@ +Using Events +==================================== + +This section is not documented yet. \ No newline at end of file diff --git a/docs_source/packs/pack.rst b/docs_source/packs/pack.rst new file mode 100644 index 00000000..1fee19e0 --- /dev/null +++ b/docs_source/packs/pack.rst @@ -0,0 +1,12 @@ +Creating a Royalnet Pack +==================================== + +This section is not documented yet. + +.. toctree:: + :maxdepth: 5 + + command + star + event + table diff --git a/docs_source/packs/star.rst b/docs_source/packs/star.rst new file mode 100644 index 00000000..ae07f784 --- /dev/null +++ b/docs_source/packs/star.rst @@ -0,0 +1,4 @@ +Adding a Star to the Pack +==================================== + +This section is not documented yet. diff --git a/docs_source/packs/table.rst b/docs_source/packs/table.rst new file mode 100644 index 00000000..9cef0afd --- /dev/null +++ b/docs_source/packs/table.rst @@ -0,0 +1,4 @@ +Using Tables and databases +==================================== + +This section is not documented yet. diff --git a/poetry.lock b/poetry.lock index 7ec23df1..4fa9e774 100644 --- a/poetry.lock +++ b/poetry.lock @@ -162,7 +162,7 @@ tzlocal = "*" category = "main" description = "A library to handle automated deprecations" name = "deprecation" -optional = false +optional = true python-versions = "*" version = "2.0.7" @@ -197,7 +197,7 @@ version = "0.16" category = "main" description = "Python audio data toolkit (ID3 and MP3)" name = "eyed3" -optional = false +optional = true python-versions = "*" version = "0.9" @@ -228,7 +228,7 @@ dev = ["future (0.17.1)", "numpy (1.16.4)", "pytest-mock (1.10.4)", "pytest (4.6 category = "main" description = "Infer file type and MIME type of any file/buffer. No external dependencies." name = "filetype" -optional = false +optional = true python-versions = "*" version = "1.0.5" @@ -318,7 +318,7 @@ description = "A very fast and expressive template engine." name = "jinja2" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.11.0" +version = "2.11.1" [package.dependencies] MarkupSafe = ">=0.23" @@ -473,7 +473,7 @@ description = "Cryptographic library for Python" name = "pycryptodome" optional = true python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.9.4" +version = "3.9.6" [[package]] category = "dev" @@ -915,7 +915,7 @@ version = "2020.1.24" [extras] alchemy_easy = ["sqlalchemy", "psycopg2_binary"] alchemy_hard = ["sqlalchemy", "psycopg2"] -bard = ["ffmpeg_python", "youtube_dl"] +bard = ["ffmpeg_python", "youtube_dl", "eyed3"] coloredlogs = ["coloredlogs"] constellation = ["starlette", "uvicorn", "python-multipart"] discord = ["discord.py", "pynacl"] @@ -925,7 +925,7 @@ sentry = ["sentry_sdk"] telegram = ["python_telegram_bot"] [metadata] -content-hash = "e46d65bd8228040eb92de09eb31a8a0987a1afbc6f39e62656f9a306df049ba4" +content-hash = "f275cd948fe28423a90d37d2825eabfec97e8ac0cdf52ee2d20f803d61987b40" python-versions = "^3.8" [metadata.files] @@ -1104,8 +1104,8 @@ imagesize = [ {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] jinja2 = [ - {file = "Jinja2-2.11.0-py2.py3-none-any.whl", hash = "sha256:6e7a3c2934694d59ad334c93dd1b6c96699cf24c53fdb8ec848ac6b23e685734"}, - {file = "Jinja2-2.11.0.tar.gz", hash = "sha256:d6609ae5ec3d56212ca7d802eda654eaf2310000816ce815361041465b108be4"}, + {file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"}, + {file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"}, ] jsonschema = [ {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, @@ -1248,38 +1248,36 @@ pycparser = [ {file = "pycparser-2.19.tar.gz", hash = "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"}, ] pycryptodome = [ - {file = "pycryptodome-3.9.4-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:6c2720696b10ae356040e888bde1239b8957fe18885ccf5e7b4e8dec882f0856"}, - {file = "pycryptodome-3.9.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:5c485ed6e9718ebcaa81138fa70ace9c563d202b56a8cee119b4085b023931f5"}, - {file = "pycryptodome-3.9.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:56fdd0e425f1b8fd3a00b6d96351f86226674974814c50534864d0124d48871f"}, - {file = "pycryptodome-3.9.4-cp27-cp27m-win32.whl", hash = "sha256:2de33ed0a95855735d5a0fc0c39603314df9e78ee8bbf0baa9692fb46b3b8bbb"}, - {file = "pycryptodome-3.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:eec0689509389f19875f66ae8dedd59f982240cdab31b9f78a8dc266011df93a"}, - {file = "pycryptodome-3.9.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:68fab8455efcbfe87c5d75015476f9b606227ffe244d57bfd66269451706e899"}, - {file = "pycryptodome-3.9.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4b9533d4166ca07abdd49ce9d516666b1df944997fe135d4b21ac376aa624aff"}, - {file = "pycryptodome-3.9.4-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:d3fe3f33ad52bf0c19ee6344b695ba44ffbfa16f3c29ca61116b48d97bd970fb"}, - {file = "pycryptodome-3.9.4-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:319e568baf86620b419d53063b18c216abf924875966efdfe06891b987196a45"}, - {file = "pycryptodome-3.9.4-cp34-cp34m-win32.whl", hash = "sha256:042ae873baadd0c33b4d699a5c5b976ade3233a979d972f98ca82314632d868c"}, - {file = "pycryptodome-3.9.4-cp34-cp34m-win_amd64.whl", hash = "sha256:a30f501bbb32e01a49ef9e09ca1260e5ab49bf33a257080ec553e08997acc487"}, - {file = "pycryptodome-3.9.4-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:b55c60c321ac91945c60a40ac9896ac7a3d432bb3e8c14006dfd82ad5871c331"}, - {file = "pycryptodome-3.9.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:9d9945ac8375d5d8e60bd2a2e1df5882eaa315522eedf3ca868b1546dfa34eba"}, - {file = "pycryptodome-3.9.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:4372ec7518727172e1605c0843cdc5375d4771e447b8148c787b860260aae151"}, - {file = "pycryptodome-3.9.4-cp35-cp35m-win32.whl", hash = "sha256:0502876279772b1384b660ccc91563d04490d562799d8e2e06b411e2d81128a9"}, - {file = "pycryptodome-3.9.4-cp35-cp35m-win_amd64.whl", hash = "sha256:72166c2ac520a5dbd2d90208b9c279161ec0861662a621892bd52fb6ca13ab91"}, - {file = "pycryptodome-3.9.4-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:b4af098f2a50f8d048ab12cabb59456585c0acf43d90ee79782d2d6d0ed59dba"}, - {file = "pycryptodome-3.9.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:8a799bea3c6617736e914a2e77c409f52893d382f619f088f8a80e2e21f573c1"}, - {file = "pycryptodome-3.9.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7c52308ac5b834331b2f107a490b2c27de024a229b61df4cdc5c131d563dfe98"}, - {file = "pycryptodome-3.9.4-cp36-cp36m-win32.whl", hash = "sha256:63c103a22cbe9752f6ea9f1a0de129995bad91c4d03a66c67cffcf6ee0c9f1e1"}, - {file = "pycryptodome-3.9.4-cp36-cp36m-win_amd64.whl", hash = "sha256:54456cf85130e01674d21fb1ab89ffccacb138a8ade88d72fa2b0ac898d2798b"}, - {file = "pycryptodome-3.9.4-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:aec4d42deb836b8fb3ba32f2ba1ef0d33dd3dc9d430b1479ee7a914490d15b5e"}, - {file = "pycryptodome-3.9.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:896e9b6fd0762aa07b203c993fbbee7a1f1a4674c6886afd7bfa86f3d1be98a8"}, - {file = "pycryptodome-3.9.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:57b1b707363490c495ad0eeb38bd1b0e1697c497af25fad78d3a1ebf0477fd5b"}, - {file = "pycryptodome-3.9.4-cp37-cp37m-win32.whl", hash = "sha256:87d8d85b4792ca5e730fb7a519fbc3ed976c59dcf79c5204589c59afd56b9926"}, - {file = "pycryptodome-3.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:e3a79a30d15d9c7c284a7734036ee8abdb5ca3a6f5774d293cdc9e1358c1dc10"}, - {file = "pycryptodome-3.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:48821950ffb9c836858d8fa09d7840b6df52eadd387a3c5acece55cb387743f9"}, - {file = "pycryptodome-3.9.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cbfd97f9e060f0d30245cd29fa267a9a84de9da97559366fca0a3f7655acc63f"}, - {file = "pycryptodome-3.9.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9ef966c727de942de3e41aa8462c4b7b4bca70f19af5a3f99e31376589c11aac"}, - {file = "pycryptodome-3.9.4-cp38-cp38-win32.whl", hash = "sha256:a8ca2450394d3699c9f15ef25e8de9a24b401933716a1e39d37fa01f5fe3c58b"}, - {file = "pycryptodome-3.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:c53348358408d94869059e16fba5ff3bef8c52c25b18421472aba272b9bb450f"}, - {file = "pycryptodome-3.9.4.tar.gz", hash = "sha256:a168e73879619b467072509a223282a02c8047d932a48b74fbd498f27224aa04"}, + {file = "pycryptodome-3.9.6-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:5029c46b0d41dfb763c3981c0af68eab029f06fe2b94f2299112fc18cf9e8d6d"}, + {file = "pycryptodome-3.9.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:95d324e603c5cec5d89e8595236bbf59ade5fe3a72d100ce61eebb323d598750"}, + {file = "pycryptodome-3.9.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2a57daef18a2022a5e4b6f7376c9ddd0c2d946e4b1f1e59b837f5bf295be7380"}, + {file = "pycryptodome-3.9.6-cp27-cp27m-win32.whl", hash = "sha256:a719bd708207fa219fcbf4c8ebbcbc52846045f78179d00445b429fdabdbc1c4"}, + {file = "pycryptodome-3.9.6-cp27-cp27m-win_amd64.whl", hash = "sha256:39e5ca2f66d1eac7abcba5ce1a03370d123dc6085620f1cd532dfee27e650178"}, + {file = "pycryptodome-3.9.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f4d2174e168d0eabd1fffaf88b4f62c2b6f30a67b8816f31024b8e48be3e2d75"}, + {file = "pycryptodome-3.9.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ec7d39589f9cfc2a8b83b1d2fc673441757c99d43283e97b2dd46e0e23730db8"}, + {file = "pycryptodome-3.9.6-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:9163fec630495c10c767991e3f8dab32f4427bfb2dfeaa59bb28fe3e52ba66f2"}, + {file = "pycryptodome-3.9.6-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:0a8d5f2dbb4bbe830ace54286b829bfa529f0853bedaab6225fcb2e6d1f7e356"}, + {file = "pycryptodome-3.9.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:5817c0b3c263025d851da96b90cbc7e95348008f88b990e90d10683dba376666"}, + {file = "pycryptodome-3.9.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c41b7e10b72cef00cd63410f31fe50e72dc3a40eafbd146e288384fbe4208064"}, + {file = "pycryptodome-3.9.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f3204006869ab037604b1d9f045c4e84882ddd365e4ee8caa5eb1ff47a59188e"}, + {file = "pycryptodome-3.9.6-cp35-cp35m-win32.whl", hash = "sha256:cdb0ad83a5d6bac986a37fcb7562bcbef0aabae8ea19505bab5cf83c4d18af12"}, + {file = "pycryptodome-3.9.6-cp35-cp35m-win_amd64.whl", hash = "sha256:1259b8ca49662b8a941177357f08147d858595c0042e63ff81e9628e925b5c9d"}, + {file = "pycryptodome-3.9.6-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:09bf05a489fe10f9280a5e0163f195e7b9630cafb15f7d72fb9c8f5eb2afa84f"}, + {file = "pycryptodome-3.9.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fcff8c9d88d58880f7eda2139c7c444552a38f98a9e77ba5970b6e78f54ac358"}, + {file = "pycryptodome-3.9.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9948c2d5c5c0ee45ed44cee0e2eba2ce60a03be006ed3074521f3da3be162e72"}, + {file = "pycryptodome-3.9.6-cp36-cp36m-win32.whl", hash = "sha256:79320f1fc5c9ca682869087c565bb29ca6f334692e940d7365771e9a94382e12"}, + {file = "pycryptodome-3.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:d8e480f65ac7105cbc288eec2417dc61eaac6ed6e75595aa15b8c7c77c53a68b"}, + {file = "pycryptodome-3.9.6-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:07daddb98f98f771ba027f8f835bdb675aeb84effe41ed5221f520b267429354"}, + {file = "pycryptodome-3.9.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:da2d581da279bc7408d38e16ff77754f5448c4352f2acfe530a5d14d8fc6934a"}, + {file = "pycryptodome-3.9.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:012ca77c2105600e3c6aef43188101ac1d95052c633a4ae8fbebffab20c25f8a"}, + {file = "pycryptodome-3.9.6-cp37-cp37m-win32.whl", hash = "sha256:05b4d865710f9a6378d3ada28195ff78e52642d3ecffe6fa9d379d870b9bf29d"}, + {file = "pycryptodome-3.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:9927aa8a8cb4af681279b6f28a1dcb14e0eb556c1aea8413a1e27608a8516e0c"}, + {file = "pycryptodome-3.9.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de61091dd68326b600422cf731eb4810c4c6363f18a65bccd6061784b7454f5b"}, + {file = "pycryptodome-3.9.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:238d8b6dd27bd1a04816a68aa90a739e6dd23b192fcd83b50f9360958bff192a"}, + {file = "pycryptodome-3.9.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d516df693c195b8da3795e381429bd420e87081b7e6c2871c62c9897c812cda"}, + {file = "pycryptodome-3.9.6-cp38-cp38-win32.whl", hash = "sha256:3e486c5b7228e864665fc479e9f596b2547b5fe29c6f5c8ed3807784d06faed7"}, + {file = "pycryptodome-3.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:887d08beca6368d3d70dc75126607ad76317a9fd07fe61323d8c3cb42add12b6"}, + {file = "pycryptodome-3.9.6.tar.gz", hash = "sha256:bc22ced26ebc46546798fa0141f4418f1db116dec517f0aeaecec87cf7b2416c"}, ] pygments = [ {file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"},