diff --git a/docs/doctrees/apireference.doctree b/docs/doctrees/apireference.doctree index c05672cc..af5a5f9c 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 71aaa60f..b9af25bf 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 3a92692f..8ff10786 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 index 610c7957..21c2ebd1 100644 Binary files a/docs/doctrees/packs/command.doctree and b/docs/doctrees/packs/command.doctree differ diff --git a/docs/doctrees/packs/event.doctree b/docs/doctrees/packs/event.doctree index 9574a22a..aa8b86c6 100644 Binary files a/docs/doctrees/packs/event.doctree and b/docs/doctrees/packs/event.doctree differ diff --git a/docs/doctrees/packs/newpack.doctree b/docs/doctrees/packs/newpack.doctree index 6aca26d8..badbb2fd 100644 Binary files a/docs/doctrees/packs/newpack.doctree and b/docs/doctrees/packs/newpack.doctree differ diff --git a/docs/doctrees/packs/pack.doctree b/docs/doctrees/packs/pack.doctree index ec7a0aef..331501cd 100644 Binary files a/docs/doctrees/packs/pack.doctree and b/docs/doctrees/packs/pack.doctree differ diff --git a/docs/doctrees/packs/star.doctree b/docs/doctrees/packs/star.doctree index 0327d8c4..192ce42c 100644 Binary files a/docs/doctrees/packs/star.doctree and b/docs/doctrees/packs/star.doctree differ diff --git a/docs/doctrees/packs/table.doctree b/docs/doctrees/packs/table.doctree index 053913ea..6a2e2ff4 100644 Binary files a/docs/doctrees/packs/table.doctree and b/docs/doctrees/packs/table.doctree differ diff --git a/docs/doctrees/randomdiscoveries.doctree b/docs/doctrees/randomdiscoveries.doctree index a610086c..c8c096ea 100644 Binary files a/docs/doctrees/randomdiscoveries.doctree and b/docs/doctrees/randomdiscoveries.doctree differ diff --git a/docs/html/.buildinfo b/docs/html/.buildinfo index b64492cb..775d3d25 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: 72c039d18ea46f966a2534df3d8861fa +config: 6f503147de72a3eb83d559fec1110ecd tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/html/_sources/index.rst.txt b/docs/html/_sources/index.rst.txt index 4e36cbb3..b28286e0 100644 --- a/docs/html/_sources/index.rst.txt +++ b/docs/html/_sources/index.rst.txt @@ -14,5 +14,5 @@ Welcome to the documentation of Royalnet! Some useful links ------------------------------------ -* `Royalnet on GitHub `_ +* `Royalnet on GitHub `_ * :ref:`genindex` \ No newline at end of file diff --git a/docs/html/_sources/packs/command.rst.txt b/docs/html/_sources/packs/command.rst.txt index 84cfef68..09766f2f 100644 --- a/docs/html/_sources/packs/command.rst.txt +++ b/docs/html/_sources/packs/command.rst.txt @@ -3,7 +3,7 @@ Creating a new Command ==================================== -A Royalnet Command is a small script that is run whenever a specific message is sent to a Royalnet interface. +A Royalnet Command is a small script that is run whenever a specific message is sent to a Royalnet platform. A Command code looks like this: :: @@ -14,12 +14,12 @@ A Command code looks like this: :: description = "Play ping-pong with the bot." - def __init__(self, interface): - # This code is run just once, while the bot is starting - super().__init__() + # This code is run just once, while the bot is starting + def __init__(self, serf: "Serf", config): + super().__init__(serf=serf, config=config) + # This code is run every time the command is called 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 @@ -32,7 +32,7 @@ Try to keep the name as short as possible, while staying specific enough so no o 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: :: +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 @@ -48,9 +48,9 @@ Inside the class, override the attributes ``name`` and ``description`` with resp 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. +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: :: +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 @@ -76,13 +76,32 @@ command to the ``available_commands`` list: :: # Don't change this, it should automatically generate __all__ __all__ = [command.__name__ for command in available_commands] +Formatting command replies +------------------------------------ + +You can use a subset of `BBCode `_ to format messages sent with :meth:`~CommandData.reply`: :: + + async def run(self, args: rc.CommandArgs, data: rc.CommandData): + await data.reply("[b]Bold of you to assume that my code has no bugs.[/b]") + +Available tags +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Here's a list of all tags that can be used: + +- ``[b]bold[/b]`` +- ``[i]italic[/i]`` +- ``[c]code[/c]`` +- ``[p]multiline \n code[/p]`` +- ``[url=https://google.com]inline link[/url]`` (will be rendered differently on every platform) + 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` +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 @@ -96,10 +115,15 @@ ones. :: description = "Send a spaghetti emoji in the chat." - syntax = "(requestedpasta)" + syntax = "{first_pasta} [second_pasta]" async def run(self, args: rc.CommandArgs, data: rc.CommandData): - await data.reply(f"🍝 Here's your {args[0]}!") + first_pasta = args[0] + second_pasta = args.optional(1) + if second_pasta is None: + await data.reply(f"🍝 Here's your {first_pasta}!") + else: + await data.reply(f"🍝 Here's your {first_pasta} and your {second_pasta}!") Direct access @@ -124,7 +148,7 @@ If you request an argument with a certain number, but the argument does not exis Optional access ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you don't want arguments to be required, you can access them through the :meth:`CommandArgs.optional` method: it +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) @@ -144,7 +168,7 @@ You can specify a default result too, so that the method will return it instead Full string ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you want the full argument string, you can use the :meth:`CommandArgs.joined` method. :: +If you want the full argument string, you can use the :meth:`~CommandArgs.joined` method. :: args.joined() # "carbonara al-dente" @@ -160,7 +184,7 @@ 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, +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. :: @@ -180,25 +204,25 @@ To match a pattern, :func:`re.match` is used, meaning that Python will try to ma 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 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: +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. + *Additionally displays the command syntax in the error message.* :exc:`.UnsupportedError` - The command is not supported on the interface it is being called. + The command is not supported on the platform it is being called. :exc:`.ConfigurationError` - The ``config.toml`` file was misconfigured (a value is missing or invalid). + A value is missing or invalid in the ``config.toml`` section of your pack. :exc:`.ExternalError` An external API the command depends on is unavailable or returned an error. @@ -211,7 +235,7 @@ 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 +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 @@ -242,13 +266,13 @@ 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` +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 +Not all platforms support deleting messages; by default, if the platform 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 @@ -262,10 +286,31 @@ to True: :: else: await data.reply("βœ… The message was deleted!") -Using the database +Sharing data between multiple calls ------------------------------------ -Bots can be connected to a PostgreSQL database through a special SQLAlchemy interface called +The :class:`~Command` class is shared between multiple command calls: if you need to store some data, you may store it as a protected/private field of your command class: :: + + class SpaghettiCommand(rc.Command): + name = "spaghetti" + + description = "Send a spaghetti emoji in the chat." + + syntax = "(requestedpasta)" + + __total_spaghetti = 0 + + async def run(self, args: rc.CommandArgs, data: rc.CommandData): + self.__total_spaghetti += 1 + await data.reply(f"🍝 Here's your {args[0]}!\n" + f"[i]Spaghetti have been served {self.__total_spaghetti} times.[/i]") + +Values stored in this way persist **only until the bot is restarted**, and **won't be shared between different serfs**; if you need persistent values, it is recommended to use a database through the Alchemy service. + +Using the Alchemy +------------------------------------ + +Royalnet 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 @@ -344,19 +389,115 @@ You can read more about sqlalchemy at their `website None: + await data.reply("Spaghetti were ejected from your floppy drive!") + +To create a new key, you can use the :class:`~KeyboardKey` class: :: + + key = KeyboardKey( + short="⏏️", # An emoji representing the key on platforms the full message cannot be displayed + text="Eject spaghetti from the floppy drive", # The text displayed on the key + callback=answer # The coroutine to call when the key is pressed. + ) + +To display a keyboard and wait for a keyboard press, you can use the :meth:`~CommandData.keyboard` asynccontextmanager. +While the contextmanager is in scope, the keyboard will be valid and it will be possible to interact with it. +Any further key pressed will be answered with an error message. :: + + async with data.keyboard(text="What kind of spaghetti would you want to order?", keys=keyboard): + # This will keep the keyboard valid for 10 seconds + await asyncio.sleep(10) + +Replies in callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Calls to :meth:`~CommandData.reply` made with the :class:`~CommandData` of a keyboard callback won't always result in a message being sent: for example, on Telegram, replies will result in a small message being displayed on the top of the screen. + +Reading data from the configuration file --------------------------------------------- -This section is not documented yet. +You can read data from your pack's configuration section through the :attr:`~Command.config` attribute: :: + + [Packs."spaghettipack"] + spaghetti = { mode="al_dente", two=true } + +:: + + await data.reply(f"Here's your spaghetti {self.config['spaghetti']['mode']}!") + +Running code on Serf start +---------------------------------------------- + +The code inside ``__init__`` is run only once, during the initialization step of the bot: :: + + def __init__(self, serf: "Serf", config): + super().__init__(serf=serf, config=config) + + # The contents of this variable will be persisted across command calls + self.persistent_variable = 0 + + # The text will be printed only if the config flag is set to something + if config["spaghetti"]["two"]: + print("Famme due spaghi!") + +.. note:: Some methods may be unavailable during the initialization of the Serf. Running repeating jobs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This section is not documented yet. +To run a job independently from the rest of the command, you can schedule the execution of a coroutine inside ``__init__``: :: + + async def mycoroutine(): + while True: + print("Free spaghetti every 60 seconds!") + await asyncio.sleep(60) + + def __init__(self, serf: "Serf", config): + super().__init__(serf=serf, config=config) + self.loop.create_task(mycoroutine()) + +As it will be executed once for every platform Royalnet is running on, you may want to run the task only on a single platform: :: + + def __init__(self, serf: "Serf", config): + super().__init__(serf=serf, config=config) + if isinstance(self.serf, rst.TelegramSerf): + self.loop.create_task(mycoroutine()) + diff --git a/docs/html/_sources/packs/newpack.rst.txt b/docs/html/_sources/packs/newpack.rst.txt index c3f4e07a..e8d26be5 100644 --- a/docs/html/_sources/packs/newpack.rst.txt +++ b/docs/html/_sources/packs/newpack.rst.txt @@ -1,10 +1,12 @@ +.. currentmodule:: royalnet + Creating a new Pack ==================================== Prerequisites ------------------------------------ -You'll need to have `Python 3.8 `_ and `poetry `_ +You'll need to have `Python 3.8 `_ and `poetry `_ to develop Royalnet Packs. Creating the repository @@ -13,7 +15,7 @@ Creating the repository To create a new pack, create a new repository based on the `Royalnet Pack template `_ and clone it to your workspace. -After cloning the template, run ``poetry install`` to install the dependencies for the pack, creating the ``poetry.lock`` file. +After cloning the template, run ``poetry install`` to create a virtualenv and install the dependencies for the pack in it. pyproject.toml ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -35,7 +37,7 @@ examplepack The ``examplepack`` folder contains the source code of your pack, and should be renamed to the name you set in the ``pyproject.toml`` file. -It should contain a ``version.py`` file and six folders: :: +It should contain six folders: :: examplepack β”œβ”€β”€ commands @@ -43,19 +45,7 @@ It should contain a ``version.py`` file and six folders: :: β”œβ”€β”€ stars β”œβ”€β”€ tables β”œβ”€β”€ types - β”œβ”€β”€ utils - └── version.py - -version.py -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``version.py`` file contains the version number of your pack. - -If you changed the ``version`` field in the ``pyproject.toml`` file, change the value of ``semantic`` in ``version.py`` to the same value. - -Remember to use `semantic versioning `_! :: - - semantic = "1.0.0" + └── utils The commands folder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -108,13 +98,25 @@ However, its files are **forbidden from importing anything else** from the rest Adding new dependencies to the Pack ------------------------------------ -As the Pack is actually a Python package, you can use ``poetry`` (or ``pip``) to add new dependencies! +As the Pack is actually a Python package, you can use ``poetry`` to add new dependencies! Use ``poetry add packagename`` to add and install a new dependency from the PyPI. +Updating the dependencies +------------------------------------ + +You can update all your dependencies by using: ``poetry update``. + +The README.md file +------------------------------------ + +The README.md file is the first thing that users of your pack will see! + +It's recommended to describe accurately how to install and configure the pack, so other users will be able to use it too! + Publishing the pack ------------------------------------ -To publish your Pack on the PyPI, run ``poetry build``, then ``poetry publish``. +To publish your Pack on the PyPI, run ``poetry publish --build``. -Poetry will build your Pack and upload it to the PyPI for you. \ No newline at end of file +Poetry will build your Pack and upload it to the PyPI for you! diff --git a/docs/html/_static/RoyalnetOpenGraph.svg b/docs/html/_static/RoyalnetOpenGraph.svg new file mode 100644 index 00000000..fa978ded --- /dev/null +++ b/docs/html/_static/RoyalnetOpenGraph.svg @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + Royalnet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/html/_static/RoyalnetTemplateOpenGraph.svg b/docs/html/_static/RoyalnetTemplateOpenGraph.svg new file mode 100644 index 00000000..3db5633e --- /dev/null +++ b/docs/html/_static/RoyalnetTemplateOpenGraph.svg @@ -0,0 +1,319 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + Royalnet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/html/_static/basic.css b/docs/html/_static/basic.css index b04360d6..01192852 100644 --- a/docs/html/_static/basic.css +++ b/docs/html/_static/basic.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- basic theme. * - * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/docs/html/_static/doctools.js b/docs/html/_static/doctools.js index b33f87fc..daccd209 100644 --- a/docs/html/_static/doctools.js +++ b/docs/html/_static/doctools.js @@ -4,7 +4,7 @@ * * Sphinx JavaScript utilities for all documentation. * - * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -283,10 +283,11 @@ var Documentation = { }, initOnKeyListeners: function() { - $(document).keyup(function(event) { + $(document).keydown(function(event) { var activeElementType = document.activeElement.tagName; // don't navigate when in search box or textarea - if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { switch (event.keyCode) { case 37: // left var prevHref = $('link[rel="prev"]').prop('href'); diff --git a/docs/html/_static/documentation_options.js b/docs/html/_static/documentation_options.js index ae25aa96..66f60afe 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.6.2', + VERSION: '5.10.4', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/html/_static/language_data.js b/docs/html/_static/language_data.js index 5266fb19..d2b4ee91 100644 --- a/docs/html/_static/language_data.js +++ b/docs/html/_static/language_data.js @@ -5,7 +5,7 @@ * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. * - * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/docs/html/_static/searchtools.js b/docs/html/_static/searchtools.js index ad845872..d11b33a7 100644 --- a/docs/html/_static/searchtools.js +++ b/docs/html/_static/searchtools.js @@ -4,7 +4,7 @@ * * Sphinx JavaScript utilities for the full-text search. * - * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -63,6 +63,11 @@ var Search = { htmlElement.innerHTML = htmlString; $(htmlElement).find('.headerlink').remove(); docContent = $(htmlElement).find('[role=main]')[0]; + if(docContent === undefined) { + console.warn("Content block not found. Sphinx search tries to obtain it " + + "via '[role=main]'. Could you check your theme or template."); + return ""; + } return docContent.textContent || docContent.innerText; }, @@ -245,6 +250,7 @@ var Search = { if (results.length) { var item = results.pop(); var listItem = $('
  • '); + var requestUrl = ""; if (DOCUMENTATION_OPTIONS.BUILDER === 'dirhtml') { // dirhtml builder var dirname = item[0] + '/'; @@ -253,15 +259,15 @@ var Search = { } else if (dirname == 'index/') { dirname = ''; } - listItem.append($('').attr('href', - DOCUMENTATION_OPTIONS.URL_ROOT + dirname + - highlightstring + item[2]).html(item[1])); + requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + dirname; + } else { // normal html builders - listItem.append($('').attr('href', - item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX + - highlightstring + item[2]).html(item[1])); + requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX; } + listItem.append($('').attr('href', + requestUrl + + highlightstring + item[2]).html(item[1])); if (item[3]) { listItem.append($(' (' + item[3] + ')')); Search.output.append(listItem); @@ -269,7 +275,7 @@ var Search = { displayNextItem(); }); } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) { - $.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX, + $.ajax({url: requestUrl, dataType: "text", complete: function(jqxhr, textstatus) { var data = jqxhr.responseText; diff --git a/docs/html/apireference.html b/docs/html/apireference.html index 9efa7566..29200b2f 100644 --- a/docs/html/apireference.html +++ b/docs/html/apireference.html @@ -8,7 +8,7 @@ - API Reference — Royalnet 5.6.2 documentation + API Reference — Royalnet 5.10.4 documentation @@ -21,10 +21,10 @@ - - - - + + + + @@ -60,7 +60,7 @@
    - 5.6.2 + 5.10.4
    @@ -181,8 +181,8 @@ then run:

    class royalnet.alchemy.Alchemy(database_uri: str, tables: Set)ΒΆ
    -

    A wrapper around sqlalchemy.orm that allows the instantiation of multiple engines at once while maintaining -a single declarative class for all of them.

    +

    A wrapper around sqlalchemy.orm that allows the instantiation of multiple engines at once while +maintaining a single declarative class for all of them.

    __init__(database_uri: str, tables: Set)ΒΆ
    @@ -214,7 +214,7 @@ Check the tables submodule for more details.

    -session_acm()ΒΆ
    +session_acm() → AsyncIterator[sqlalchemy.orm.session.Session]ΒΆ

    Create a Session as a async context manager (that can be used in async with statements).

    The Session will be closed safely when the context manager exits (even in case of error).

    Example

    @@ -230,7 +230,7 @@ Check the tables submodule for more details.

    -session_cm()ΒΆ
    +session_cm() → Iterator[sqlalchemy.orm.session.Session]ΒΆ

    Create a Session as a context manager (that can be used in with statements).

    The Session will be closed safely when the context manager exits (even in case of error).

    Example

    @@ -298,7 +298,7 @@ Check the tables submodule for more details.

    -classmethod from_url(url, loop: Optional[asyncio.events.AbstractEventLoop] = None, **ytdl_args) → List[royalnet.bard.ytdlinfo.YtdlInfo]ΒΆ
    +async classmethod from_url(url, loop: Optional[asyncio.events.AbstractEventLoop] = None, **ytdl_args) → List[royalnet.bard.ytdlinfo.YtdlInfo]ΒΆ

    Fetch the info for an url through YoutubeDL.

    Returns
    @@ -354,7 +354,7 @@ Check the tables submodule for more details.

    -classmethod from_url(url: str, **ytdl_args) → List[royalnet.bard.ytdlfile.YtdlFile]ΒΆ
    +async classmethod from_url(url: str, **ytdl_args) → List[royalnet.bard.ytdlfile.YtdlFile]ΒΆ

    Create a list of YtdlFile from a URL.

    @@ -411,84 +411,9 @@ Check the tables submodule for more details.

    CommandsΒΆ

    The subpackage providing all classes related to Royalnet commands.

    -
    -
    -class royalnet.commands.CommandInterface(config: Dict[str, Any])ΒΆ
    -
    -
    -property alchemyΒΆ
    -

    A shortcut for serf.alchemy.

    -
    - -
    -
    -async call_herald_event(destination: str, event_name: str, **kwargs) → dictΒΆ
    -

    Call an event function on a different Serf.

    -

    Example

    -

    You can run a function on a DiscordSerf from a -TelegramSerf.

    -
    - -
    -
    -config = NoneΒΆ
    -

    The config section for the pack of the command.

    -
    - -
    -
    -constellation = NoneΒΆ
    -

    A reference to the Constellation that is implementing this CommandInterface.

    -

    Example

    -

    A reference to a Constellation.

    -
    - -
    -
    -property loopΒΆ
    -

    A shortcut for serf.loop.

    -
    - -
    -
    -name = NotImplementedΒΆ
    -

    The name of the CommandInterface that’s being implemented.

    -

    Examples

    -

    telegram, discord, console…

    -
    - -
    -
    -prefix = NotImplementedΒΆ
    -

    The prefix used by commands on the interface.

    -

    Examples

    -

    / on Telegram, ! on Discord.

    -
    - -
    -
    -serf = NoneΒΆ
    -

    A reference to the Serf that is implementing this CommandInterface.

    -

    Example

    -

    A reference to a TelegramSerf.

    -
    - -
    -
    -property tableΒΆ
    -

    A shortcut for serf.alchemy.get().

    -
    -
    Raises
    -

    UnsupportedError – if alchemy is None.

    -
    -
    -
    - -
    -
    -class royalnet.commands.Command(interface: royalnet.commands.commandinterface.CommandInterface)ΒΆ
    +class royalnet.commands.Command(serf: Serf, config: ConfigDict)ΒΆ
    property alchemyΒΆ
    @@ -497,21 +422,15 @@ Check the tables submodule for more details.

    -aliases = []ΒΆ
    +aliases: List[str] = []ΒΆ

    A list of possible aliases for a command.

    Example

    To be able to call /e as an alias for /example, one should set aliases to ["e"].

    -
    -
    -property configΒΆ
    -

    A shortcut for interface.config.

    -
    -
    -description = NotImplementedΒΆ
    +description: str = NotImplementedΒΆ

    A small description of the command, to be displayed when the command is being autocompleted.

    @@ -523,7 +442,7 @@ Check the tables submodule for more details.

    -name = NotImplementedΒΆ
    +name: str = NotImplementedΒΆ

    The main name of the command.

    Example

    To be able to call /example on Telegram, the name should be "example".

    @@ -531,18 +450,12 @@ Check the tables submodule for more details.

    -async run(args: royalnet.commands.commandargs.CommandArgs, data: royalnet.commands.commanddata.CommandData) → NoneΒΆ
    +abstract async run(args: royalnet.commands.commandargs.CommandArgs, data: royalnet.commands.commanddata.CommandData) → NoneΒΆ
    -
    -
    -property serfΒΆ
    -

    A shortcut for interface.serf.

    -
    -
    -syntax = ''ΒΆ
    +syntax: str = ''ΒΆ

    The syntax of the command, to be displayed when a InvalidInputError is raised, in the format (required_arg) [optional_arg].

    @@ -551,7 +464,7 @@ in the format (requ
    -class royalnet.commands.CommandData(interface: royalnet.commands.commandinterface.CommandInterface, loop: asyncio.events.AbstractEventLoop)ΒΆ
    +class royalnet.commands.CommandData(command)ΒΆ
    async delete_invoking(error_if_unavailable=False) → NoneΒΆ
    @@ -566,7 +479,7 @@ in the format (requ
    -async find_user(alias: str) → Optional[User]ΒΆ
    +async find_user(alias: str) → Optional[royalnet.backpack.tables.users.User]ΒΆ

    Find the User having a specific Alias.

    Parameters
    @@ -592,6 +505,16 @@ That probably means, the database row identifying the user.

    keyboard(text, keys: List[KeyboardKey])ΒΆ
    +
    +
    +property loopΒΆ
    +
    + +
    +
    +classmethod register_keyboard_key(identifier: str, key: KeyboardKey)ΒΆ
    +
    +
    async reply(text: str) → NoneΒΆ
    @@ -603,6 +526,20 @@ That probably means, the database row identifying the user.

    +
    +
    +async reply_image(image: io.IOBase, caption: Optional[str] = None) → NoneΒΆ
    +

    Send an image (with optionally a caption) to the channel where the call was made.

    +
    +
    Parameters
    +
      +
    • image – The bytes of the image to send.

    • +
    • caption – The caption to attach to the image.

    • +
    +
    +
    +
    +
    property sessionΒΆ
    @@ -611,15 +548,20 @@ That probably means, the database row identifying the user.

    async session_close()ΒΆ
    -

    Asyncronously close the session of this object.

    +

    Asyncronously close the session of this object.

    async session_commit()ΒΆ
    -

    Asyncronously commit the session of this object.

    +

    Asyncronously commit the session of this object.

    +
    +
    +classmethod unregister_keyboard_key(identifier: str)ΒΆ
    +
    +
    @@ -767,61 +709,37 @@ down).

    -
    -class royalnet.commands.Event(interface: royalnet.commands.commandinterface.CommandInterface)ΒΆ
    +
    +class royalnet.commands.HeraldEvent(parent: Union[Serf, Constellation], config)ΒΆ

    A remote procedure call triggered by a royalnet.herald request.

    -
    -__init__(interface: royalnet.commands.commandinterface.CommandInterface)ΒΆ
    -

    Bind the event to a Serf.

    +
    +property alchemyΒΆ
    +

    A shortcut for parent.alchemy.

    -
    -property alchemyΒΆ
    -

    A shortcut for interface.alchemy.

    -
    - -
    -
    -property configΒΆ
    -

    A shortcut for interface.config.

    +
    +property loopΒΆ
    +

    A shortcut for parent.loop.

    -
    -interface = NoneΒΆ
    -

    The CommandInterface available to this Event.

    -
    - -
    -
    -property loopΒΆ
    -

    A shortcut for interface.loop.

    -
    - -
    -
    -name = NotImplementedΒΆ
    +
    +name = NotImplementedΒΆ

    The event_name that will trigger this event.

    -
    -async run(**kwargs)ΒΆ
    +
    +async run(**kwargs)ΒΆ
    -
    -
    -property serfΒΆ
    -

    A shortcut for interface.serf.

    -
    -
    -class royalnet.commands.KeyboardKey(interface: royalnet.commands.commandinterface.CommandInterface, short: str, text: str, callback: Callable[[royalnet.commands.commanddata.CommandData], Awaitable[None]])ΒΆ
    +class royalnet.commands.KeyboardKey(short: str, text: str, callback: Callable[[royalnet.commands.commanddata.CommandData], Awaitable[None]])ΒΆ
    async press(data: royalnet.commands.commanddata.CommandData)ΒΆ
    @@ -829,6 +747,16 @@ down).

    +
    +
    +class royalnet.commands.ConfigDictΒΆ
    +
    +
    +classmethod convert(item)ΒΆ
    +
    + +
    +

    ConstellationΒΆ

    @@ -849,15 +777,9 @@ down).

    The class that represents the webserver.

    It runs multiple Star, which represent the routes of the website.

    It also handles the Alchemy connection, and Herald connections too.

    -
    -
    -Interface = NoneΒΆ
    -

    The CommandInterface class of this Constellation.

    -
    -
    -address = NoneΒΆ
    +address: str = NoneΒΆ

    The address that the Constellation will bind to when run.

    @@ -867,15 +789,22 @@ down).

    The Alchemy of this Constellation.

    +
    +
    +async call_herald_event(destination: str, event_name: str, **kwargs) → DictΒΆ
    +

    Send a royalherald.Request to a specific destination, and wait for a +royalherald.Response.

    +
    +
    -events = NoneΒΆ
    +events: Dict[str, rc.HeraldEvent] = NoneΒΆ

    A dictionary containing all Event that can be handled by this Constellation.

    -herald = NoneΒΆ
    +herald: Optional[rh.Link] = NoneΒΆ

    The Link object connecting the Constellation to the rest of the herald network. As is the case with the logging module, it will be started on the first request received by the Constellation, as the event loop won’t be available before that.

    @@ -883,7 +812,7 @@ As is the case with the logging module, it will be started on the first request
    -herald_task = NoneΒΆ
    +herald_task: Optional[aio.Task] = NoneΒΆ

    A reference to the aio.Task that runs the rh.Link.

    @@ -893,15 +822,9 @@ As is the case with the logging module, it will be started on the first request

    Create a rh.Link.

    -
    -
    -interface_factory() → Type[royalnet.commands.commandinterface.CommandInterface]ΒΆ
    -

    Create the rc.CommandInterface class for the Constellation.

    -
    -
    -loop = NoneΒΆ
    +loop: Optional[aio.AbstractEventLoop] = NoneΒΆ

    The event loop of the Constellation.

    Because of how uvicorn runs, it will stay None until the first page is requested.

    @@ -913,13 +836,13 @@ As is the case with the logging module, it will be started on the first request
    -port = NoneΒΆ
    +port: int = NoneΒΆ

    The port on which the Constellation will listen for connection on.

    -register_events(events: List[Type[royalnet.commands.event.Event]], pack_cfg: Dict[str, Any])ΒΆ
    +register_events(events: List[Type[royalnet.commands.heraldevent.HeraldEvent]], pack_cfg: Dict[str, Any])ΒΆ
    @@ -941,7 +864,7 @@ As is the case with the logging module, it will be started on the first request
    -running = NoneΒΆ
    +running: bool = NoneΒΆ

    Is the Constellation server currently running?

    @@ -953,7 +876,7 @@ As is the case with the logging module, it will be started on the first request
    -stars = NoneΒΆ
    +stars: List[PageStar] = NoneΒΆ

    A list of all the PageStar registered to this Constellation.

    @@ -961,7 +884,7 @@ As is the case with the logging module, it will be started on the first request
    -class royalnet.constellation.Star(interface: royalnet.commands.commandinterface.CommandInterface)ΒΆ
    +class royalnet.constellation.Star(constellation: Constellation, config: ConfigDict)ΒΆ

    A Star is a class representing a part of the website.

    It shouldn’t be used directly: please use PageStar and ExceptionStar instead!

    @@ -976,21 +899,9 @@ As is the case with the logging module, it will be started on the first request

    A shortcut for the Alchemy of the Constellation.

    -
    -
    -property configΒΆ
    -

    A shortcut for the Pack configuration of the Constellation.

    -
    - -
    -
    -property constellationΒΆ
    -

    A shortcut for the Constellation.

    -
    -
    -async page(request: starlette.requests.Request) → starlette.responses.ResponseΒΆ
    +abstract async page(request: starlette.requests.Request) → starlette.responses.ResponseΒΆ

    The function generating the Response to a web Request.

    If it raises an error, the corresponding ExceptionStar will be used to handle the request instead.

    @@ -1005,13 +916,13 @@ As is the case with the logging module, it will be started on the first request
    -class royalnet.constellation.PageStar(interface: royalnet.commands.commandinterface.CommandInterface)ΒΆ
    +class royalnet.constellation.PageStar(constellation: Constellation, config: ConfigDict)ΒΆ

    A PageStar is a class representing a single route of the website (for example, /api/user/get).

    To create a new website route you should create a new class inheriting from this class with a function overriding page(), path and optionally methods.

    -
    +
    -methods = ['GET']ΒΆ
    +classmethod methods()ΒΆ

    The HTTP methods supported by the Star, in form of a list.

    By default, a Star only supports the GET method, but more can be added.

    Example

    @@ -1022,7 +933,7 @@ As is the case with the logging module, it will be started on the first request
    -path = NotImplementedΒΆ
    +path: str = NotImplementedΒΆ

    The route of the star.

    Example

    path: str = '/api/user/get'
    @@ -1337,18 +1248,12 @@ Akin to the sequence number on IP packets.

    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], **_)ΒΆ
    +class royalnet.serf.Serf(loop: asyncio.events.AbstractEventLoop, alchemy_cfg: royalnet.commands.configdict.ConfigDict, herald_cfg: royalnet.commands.configdict.ConfigDict, packs_cfg: royalnet.commands.configdict.ConfigDict, **_)ΒΆ

    An abstract class, to be used as base to implement Royalnet bots on multiple interfaces (such as Telegram or Discord).

    -
    -
    -Interface = NoneΒΆ
    -

    The CommandInterface class of this Serf.

    -
    -
    -alchemy = NoneΒΆ
    +alchemy: Optional[ra.Alchemy] = NoneΒΆ

    The Alchemy object connecting this Serf to a database.

    @@ -1357,27 +1262,34 @@ Discord).

    async call(command: royalnet.commands.command.Command, data: royalnet.commands.commanddata.CommandData, parameters: List[str])ΒΆ
    +
    +
    +async call_herald_event(destination: str, event_name: str, **kwargs) → DictΒΆ
    +

    Send a royalherald.Request to a specific destination, and wait for a +royalherald.Response.

    +
    +
    -commands = NoneΒΆ
    +commands: Dict[str, rc.Command] = NoneΒΆ

    The dict connecting each command name to its Command object.

    -events = NoneΒΆ
    +events: Dict[str, rc.HeraldEvent] = NoneΒΆ

    A dictionary containing all Event that can be handled by this Serf.

    -herald = NoneΒΆ
    +herald: Optional[rh.Link] = NoneΒΆ

    The Link object connecting the Serf to the rest of the Herald network.

    -herald_task = NoneΒΆ
    +herald_task: Optional[aio.Task] = NoneΒΆ

    A reference to the asyncio.Task that runs the Link.

    @@ -1389,7 +1301,7 @@ Discord).

    -identity_table = NoneΒΆ
    +identity_table: Optional[Table] = NoneΒΆ

    The identity table containing the interface data (such as the Telegram user data) and that is in a many-to-one relationship with the master table.

    @@ -1403,16 +1315,10 @@ table and the identity table.

    -init_herald(herald_cfg: Dict[str, Any])ΒΆ
    +init_herald(herald_cfg: royalnet.commands.configdict.ConfigDict)ΒΆ

    Create a Link and bind Event.

    -
    -
    -interface_factory() → Type[royalnet.commands.commandinterface.CommandInterface]ΒΆ
    -

    Create the CommandInterface class for the Serf.

    -
    -
    interface_name = NotImplementedΒΆ
    @@ -1420,13 +1326,13 @@ table and the identity table.

    -loop = NoneΒΆ
    +loop: Optional[aio.AbstractEventLoop] = NoneΒΆ

    The event loop this Serf is running on.

    -master_table = NoneΒΆ
    +master_table: Optional[Table] = NoneΒΆ

    The central table listing all users. It usually is User.

    @@ -1435,20 +1341,25 @@ table and the identity table.

    async network_handler(message: Union[royalnet.herald.request.Request, royalnet.herald.broadcast.Broadcast]) → royalnet.herald.response.ResponseΒΆ
    +
    +
    +prefix = NotImplementedΒΆ
    +
    +
    -async press(key: royalnet.commands.keyboardkey.KeyboardKey, data: royalnet.commands.commanddata.CommandData)ΒΆ
    +async static press(key: royalnet.commands.keyboardkey.KeyboardKey, data: royalnet.commands.commanddata.CommandData)ΒΆ
    -register_commands(commands: List[Type[royalnet.commands.command.Command]], pack_cfg: Dict[str, Any]) → NoneΒΆ
    +register_commands(commands: List[Type[royalnet.commands.command.Command]], pack_cfg: royalnet.commands.configdict.ConfigDict) → NoneΒΆ

    Initialize and register all commands passed as argument.

    -register_events(events: List[Type[royalnet.commands.event.Event]], pack_cfg: Dict[str, Any])ΒΆ
    +register_events(events: List[Type[royalnet.commands.heraldevent.HeraldEvent]], pack_cfg: royalnet.commands.configdict.ConfigDict)ΒΆ
    @@ -1493,6 +1404,10 @@ table and the identity table.

    Warning

    The called function must be thread-safe!

    +
    +

    Warning

    +

    Calling a function this way might be significantly slower than calling its blocking counterpart!

    +
    @@ -1507,12 +1422,13 @@ table and the identity table.

    -royalnet.utils.andformat(l: Collection[str], middle=', ', final=' and ') → strΒΆ
    -

    Convert a iterable (such as a list) to a str by adding final between the last two elements and middle between the others.

    +royalnet.utils.andformat(coll: Collection[str], middle=', ', final=' and ') → strΒΆ +

    Convert a collection (such as a list) to a str by adding final between the last two +elements and middle between the others.

    Parameters
      -
    • l – the input iterable.

    • +
    • coll – the input collection.

    • middle – the str to be added between the middle elements.

    • final – the str to be added between the last two elements.

    @@ -1549,7 +1465,7 @@ table and the identity table.

    Example

    >>> underscorize("LE EPIC PRANK [GONE WRONG!?!?]")
    -"LE EPIC PRANK _GONE WRONG_____"
    +"LE_EPIC_PRANK__GONE_WRONG_____"
     
    @@ -1584,11 +1500,11 @@ table and the identity table.

    -royalnet.utils.numberemojiformat(l: List[str]) → strΒΆ
    -

    Convert a list to a Unicode string with one item on every line numbered with emojis.

    +royalnet.utils.numberemojiformat(li: Collection[str]) → strΒΆ +

    Convert a collection to a string with one item on every line numbered with emojis.

    Parameters
    -

    l – the list to convert.

    +

    li – the list to convert.

    Returns

    The resulting Unicode string.

    @@ -1669,11 +1585,26 @@ table and the identity table.

    royalnet.utils.sentry_exc(exc: Exception, level: str = 'ERROR')ΒΆ
    +
    +
    +royalnet.utils.sentry_wrap(level: str = 'ERROR')ΒΆ
    +
    + +
    +
    +royalnet.utils.sentry_async_wrap(level: str = 'ERROR')ΒΆ
    +
    +
    royalnet.utils.init_logging(logging_cfg: Dict[str, Any])ΒΆ
    +
    +
    +royalnet.utils.strip_tabs(s: str) → strΒΆ
    +
    + @@ -1695,7 +1626,7 @@ table and the identity table.

    - © Copyright 2019, Stefano Pigozzi + © Copyright 2020, Stefano Pigozzi

    diff --git a/docs/html/genindex.html b/docs/html/genindex.html index 50c4f228..835441a0 100644 --- a/docs/html/genindex.html +++ b/docs/html/genindex.html @@ -9,7 +9,7 @@ - Index — Royalnet 5.6.2 documentation + Index — Royalnet 5.10.4 documentation @@ -22,10 +22,10 @@ - - - - + + + + @@ -60,7 +60,7 @@
    - 5.6.2 + 5.10.4
    @@ -189,8 +189,6 @@
  • (royalnet.bard.YtdlFile method)
  • (royalnet.bard.YtdlInfo method) -
  • -
  • (royalnet.commands.Event method)
  • (royalnet.herald.Package method)
  • @@ -214,9 +212,7 @@
  • alchemy() (royalnet.commands.Command property) + - @@ -517,7 +491,7 @@
  • +
  • reply_image() (royalnet.commands.CommandData method) +
  • Request (class in royalnet.herald)
  • request() (royalnet.herald.Link method) @@ -630,11 +606,11 @@
  • route_package() (royalnet.herald.Server method)
  • royalnet.alchemy (module) -
  • -
  • royalnet.backpack (module)
    • +
    • royalnet.backpack (module) +
    • royalnet.bard (module)
    • royalnet.commands (module) @@ -650,7 +626,7 @@
    • run() (royalnet.commands.Command method)
        -
      • (royalnet.commands.Event method) +
      • (royalnet.commands.HeraldEvent method)
      • (royalnet.herald.Link method)
      • @@ -680,19 +656,15 @@ - +
        • session_acm() (royalnet.alchemy.Alchemy method)
            @@ -728,6 +700,8 @@
          • starlette (royalnet.constellation.Constellation attribute)
          • stars (royalnet.constellation.Constellation attribute) +
          • +
          • strip_tabs() (in module royalnet.utils)
          • syntax (royalnet.commands.Command attribute)
          • @@ -737,8 +711,6 @@

            T

              +
            • UnsupportedError +
            • url() (royalnet.herald.Config property)
            • UserError @@ -808,7 +782,7 @@

              - © Copyright 2019, Stefano Pigozzi + © Copyright 2020, Stefano Pigozzi

              diff --git a/docs/html/index.html b/docs/html/index.html index 8e0035a2..3b8965ec 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -8,7 +8,7 @@ - Royalnet — Royalnet 5.6.2 documentation + Royalnet — Royalnet 5.10.4 documentation @@ -21,10 +21,10 @@ - - - - + + + + @@ -60,7 +60,7 @@
              - 5.6.2 + 5.10.4
              @@ -165,7 +165,7 @@ @@ -189,7 +189,7 @@

              - © Copyright 2019, Stefano Pigozzi + © Copyright 2020, Stefano Pigozzi

              diff --git a/docs/html/objects.inv b/docs/html/objects.inv index 3357d1fa..216223d7 100644 Binary files a/docs/html/objects.inv and b/docs/html/objects.inv differ diff --git a/docs/html/packs/command.html b/docs/html/packs/command.html index 8fd8d26c..9e900895 100644 --- a/docs/html/packs/command.html +++ b/docs/html/packs/command.html @@ -8,7 +8,7 @@ - Creating a new Command — Royalnet 5.6.2 documentation + Creating a new Command — Royalnet 5.10.4 documentation @@ -21,10 +21,10 @@ - - - - + + + + @@ -61,7 +61,7 @@
              - 5.6.2 + 5.10.4
              @@ -90,6 +90,10 @@
            • Creating a new Pack
            • Creating a new Command
              • Creating a new Command
              • +
              • Formatting command replies +
              • Command arguments
                • Direct access
                • Optional access
                • @@ -100,7 +104,8 @@
                • Raising errors
                • Coroutines and slow operations
                • Delete the invoking message
                • -
                • Using the database
                    +
                  • Sharing data between multiple calls
                  • +
                  • Using the Alchemy
                  • Calling Events
                  • -
                  • Displaying Keyboards
                  • -
                  • Running code at the initialization of the bot
                      +
                    • Distinguish between platforms
                    • +
                    • Displaying Keyboards +
                    • +
                    • Reading data from the configuration file
                    • +
                    • Running code on Serf start
                    • @@ -191,7 +201,7 @@

                      Creating a new CommandΒΆ

                      -

                      A Royalnet Command is a small script that is run whenever a specific message is sent to a Royalnet interface.

                      +

                      A Royalnet Command is a small script that is run whenever a specific message is sent to a Royalnet platform.

                      A Command code looks like this:

                      import royalnet.commands as rc
                       
                      @@ -200,12 +210,12 @@
                       
                           description = "Play ping-pong with the bot."
                       
                      -    def __init__(self, interface):
                      -        # This code is run just once, while the bot is starting
                      -        super().__init__()
                      +    # This code is run just once, while the bot is starting
                      +    def __init__(self, serf: "Serf", config):
                      +        super().__init__(serf=serf, config=config)
                       
                      +    # This code is run every time the command is called
                           async def run(self, args: rc.CommandArgs, data: rc.CommandData):
                      -        # This code is run every time the command is called
                               await data.reply("Pong!")
                       
                      @@ -232,8 +242,8 @@ The previously mentioned β€œspaghetti” command should have a file called description = "Send a spaghetti emoji in the chat."
                      -

                      Now override the 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 CommandData.reply() coroutine:

                      +

                      Now override the 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 reply() coroutine:

                      import royalnet.commands as rc
                       
                       class SpaghettiCommand(rc.Command):
                      @@ -260,11 +270,30 @@ command to the avai
                       
                      +
                      +

                      Formatting command repliesΒΆ

                      +

                      You can use a subset of BBCode to format messages sent with reply():

                      +
                      async def run(self, args: rc.CommandArgs, data: rc.CommandData):
                      +    await data.reply("[b]Bold of you to assume that my code has no bugs.[/b]")
                      +
                      +
                      +
                      +

                      Available tagsΒΆ

                      +

                      Here’s a list of all tags that can be used:

                      +
                        +
                      • [b]bold[/b]

                      • +
                      • [i]italic[/i]

                      • +
                      • [c]code[/c]

                      • +
                      • [p]multiline \n code[/p]

                      • +
                      • [url=https://google.com]inline link[/url] (will be rendered differently on every platform)

                      • +
                      +
                      +

                      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 str β€œcarbonara al-dente” to the command code.

                      -

                      These arguments can be accessed in multiple ways through the args parameter passed to the Command.run() +

                      These arguments can be accessed in multiple ways through the args parameter passed to the 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 @@ -276,10 +305,15 @@ ones.

                      description = "Send a spaghetti emoji in the chat." - syntax = "(requestedpasta)" + syntax = "{first_pasta} [second_pasta]" async def run(self, args: rc.CommandArgs, data: rc.CommandData): - await data.reply(f"🍝 Here's your {args[0]}!") + first_pasta = args[0] + second_pasta = args.optional(1) + if second_pasta is None: + await data.reply(f"🍝 Here's your {first_pasta}!") + else: + await data.reply(f"🍝 Here's your {first_pasta} and your {second_pasta}!")
                      @@ -301,7 +335,7 @@ ones.

                      Optional accessΒΆ

                      -

                      If you don’t want arguments to be required, you can access them through the CommandArgs.optional() method: it +

                      If you don’t want arguments to be required, you can access them through the optional() method: it will return None if the argument wasn’t passed, making it optional.

                      args.optional(0)
                       # "carbonara"
                      @@ -321,7 +355,7 @@ will return None
                       

                      Full stringΒΆ

                      -

                      If you want the full argument string, you can use the CommandArgs.joined() method.

                      +

                      If you want the full argument string, you can use the joined() method.

                      args.joined()
                       # "carbonara al-dente"
                       
                      @@ -336,7 +370,7 @@ raised if not enough arguments are present:

                      Regular expressionsΒΆ

                      For more complex commands, you may want to get arguments through regular expressions.

                      -

                      You can then use the CommandArgs.match() method, which tries to match a pattern to the command argument string, +

                      You can then use the match() method, which tries to match a pattern to the command argument string, which returns a tuple of the matched groups and raises an InvalidInputError if there is no match.

                      To match a pattern, re.match() is used, meaning that Python will try to match only at the beginning of the string.

                      args.match(r"(carb\w+)")
                      @@ -366,11 +400,11 @@ which returns a tuple of the matched groups and raises an UserError

                      The user did something wrong, it is not a problem with the bot.

                      InvalidInputError

                      The arguments the user passed to the command by the user are invalid. -Displays the command syntax in the error message.

                      +Additionally displays the command syntax in the error message.

                      -
                      UnsupportedError

                      The command is not supported on the interface it is being called.

                      +
                      UnsupportedError

                      The command is not supported on the platform it is being called.

                      -
                      ConfigurationError

                      The config.toml file was misconfigured (a value is missing or invalid).

                      +
                      ConfigurationError

                      A value is missing or invalid in the config.toml section of your pack.

                      ExternalError

                      An external API the command depends on is unavailable or returned an error.

                      @@ -381,7 +415,7 @@ Displays the command syntax in the error message.

                      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 CommandData.reply() isn’t a simple method: it is a coroutine, a special kind of function that +

                      This is because 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.

                      @@ -408,13 +442,13 @@ 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 CommandData.delete_invoking +

                      You can have the bot delete the invoking message for a command by calling the 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 +

                      Not all platforms support deleting messages; by default, if the platform 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:

                      @@ -428,9 +462,29 @@ to True:

                      -
                      -

                      Using the databaseΒΆ

                      -

                      Bots can be connected to a PostgreSQL database through a special SQLAlchemy interface called +

                      +

                      Sharing data between multiple callsΒΆ

                      +

                      The Command class is shared between multiple command calls: if you need to store some data, you may store it as a protected/private field of your command class:

                      +
                      class SpaghettiCommand(rc.Command):
                      +    name = "spaghetti"
                      +
                      +    description = "Send a spaghetti emoji in the chat."
                      +
                      +    syntax = "(requestedpasta)"
                      +
                      +    __total_spaghetti = 0
                      +
                      +    async def run(self, args: rc.CommandArgs, data: rc.CommandData):
                      +        self.__total_spaghetti += 1
                      +        await data.reply(f"🍝 Here's your {args[0]}!\n"
                      +                         f"[i]Spaghetti have been served {self.__total_spaghetti} times.[/i]")
                      +
                      +
                      +

                      Values stored in this way persist only until the bot is restarted, and won’t be shared between different serfs; if you need persistent values, it is recommended to use a database through the Alchemy service.

                      +
                      +
                      +

                      Using the AlchemyΒΆ

                      +

                      Royalnet can be connected to a PostgreSQL database through a special SQLAlchemy interface called royalnet.alchemy.Alchemy.

                      If the connection is established, the self.alchemy and data.session fields will be available for use in commands.

                      @@ -501,18 +555,112 @@ if you want the command to raise an error if the number of results is greater th

                      Calling EventsΒΆ

                      -

                      This section is not documented yet.

                      +

                      You can call an event from inside a command, and receive its return value.

                      +

                      This may be used for example to get data from a different platform, such as getting the users online in a specific Discord server.

                      +

                      You can call an event with the Serf.call_herald_event() method:

                      +
                      result = await self.serf.call_herald_event("event_name")
                      +
                      +
                      +

                      You can also pass parameters to the called event:

                      +
                      result = await self.serf.call_herald_event("event_name", ..., kwarg=..., *..., **...)
                      +
                      +
                      +

                      Errors raised by the event will also be raised by the Serf.call_herald_event() method as one of the exceptions described in the Raising errors section.

                      +
                      +
                      +

                      Distinguish between platformsΒΆ

                      +

                      To see if a command is being run on a specific platform, you can check the type of the self.serf object:

                      +
                      import royalnet.serf.telegram as rst
                      +import royalnet.serf.discord as rsd
                      +...
                      +
                      +if isinstance(self.serf, rst.TelegramSerf):
                      +    await data.reply("This command is being run on Telegram.")
                      +elif isinstance(self.serf, rsd.DiscordSerf):
                      +    await data.reply("This command is being run on Discord.")
                      +...
                      +
                      +

                      Displaying KeyboardsΒΆ

                      -

                      This section is not documented yet.

                      +

                      A keyboard is a message with multiple buttons (β€œkeys”) attached which can be pressed by an user viewing the message.

                      +

                      Once a button is pressed, a callback function is run, which has its own CommandData context and can do everything a regular comment call could.

                      +

                      The callback function is a coroutine accepting a single data: CommandData argument:

                      +
                      async def answer(data: CommandData) -> None:
                      +    await data.reply("Spaghetti were ejected from your floppy drive!")
                      +
                      +
                      +

                      To create a new key, you can use the KeyboardKey class:

                      +
                      key = KeyboardKey(
                      +    short="⏏️",  # An emoji representing the key on platforms the full message cannot be displayed
                      +    text="Eject spaghetti from the floppy drive",  # The text displayed on the key
                      +    callback=answer  # The coroutine to call when the key is pressed.
                      +)
                      +
                      +
                      +

                      To display a keyboard and wait for a keyboard press, you can use the keyboard() asynccontextmanager. +While the contextmanager is in scope, the keyboard will be valid and it will be possible to interact with it. +Any further key pressed will be answered with an error message.

                      +
                      async with data.keyboard(text="What kind of spaghetti would you want to order?", keys=keyboard):
                      +    # This will keep the keyboard valid for 10 seconds
                      +    await asyncio.sleep(10)
                      +
                      +
                      +
                      +

                      Replies in callbacksΒΆ

                      +

                      Calls to reply() made with the CommandData of a keyboard callback won’t always result in a message being sent: for example, on Telegram, replies will result in a small message being displayed on the top of the screen.

                      +
                      +
                      +
                      +

                      Reading data from the configuration fileΒΆ

                      +

                      You can read data from your pack’s configuration section through the config attribute:

                      +
                      [Packs."spaghettipack"]
                      +spaghetti = { mode="al_dente", two=true }
                      +
                      +
                      +
                      await data.reply(f"Here's your spaghetti {self.config['spaghetti']['mode']}!")
                      +
                      +
                      +
                      +
                      +

                      Running code on Serf startΒΆ

                      +

                      The code inside __init__ is run only once, during the initialization step of the bot:

                      +
                      def __init__(self, serf: "Serf", config):
                      +    super().__init__(serf=serf, config=config)
                      +
                      +    # The contents of this variable will be persisted across command calls
                      +    self.persistent_variable = 0
                      +
                      +    # The text will be printed only if the config flag is set to something
                      +    if config["spaghetti"]["two"]:
                      +        print("Famme due spaghi!")
                      +
                      +
                      +
                      +

                      Note

                      +

                      Some methods may be unavailable during the initialization of the Serf.

                      -
                      -

                      Running code at the initialization of the botΒΆ

                      -

                      This section is not documented yet.

                      Running repeating jobsΒΆ

                      -

                      This section is not documented yet.

                      +

                      To run a job independently from the rest of the command, you can schedule the execution of a coroutine inside __init__:

                      +
                      async def mycoroutine():
                      +    while True:
                      +        print("Free spaghetti every 60 seconds!")
                      +        await asyncio.sleep(60)
                      +
                      +def __init__(self, serf: "Serf", config):
                      +    super().__init__(serf=serf, config=config)
                      +    self.loop.create_task(mycoroutine())
                      +
                      +
                      +

                      As it will be executed once for every platform Royalnet is running on, you may want to run the task only on a single platform:

                      +
                      def __init__(self, serf: "Serf", config):
                      +    super().__init__(serf=serf, config=config)
                      +    if isinstance(self.serf, rst.TelegramSerf):
                      +        self.loop.create_task(mycoroutine())
                      +
                      +
                      @@ -537,7 +685,7 @@ if you want the command to raise an error if the number of results is greater th

                      - © Copyright 2019, Stefano Pigozzi + © Copyright 2020, Stefano Pigozzi

                      diff --git a/docs/html/packs/event.html b/docs/html/packs/event.html index 962b5f54..cf9c4f6b 100644 --- a/docs/html/packs/event.html +++ b/docs/html/packs/event.html @@ -8,7 +8,7 @@ - Using Events — Royalnet 5.6.2 documentation + Using Events — Royalnet 5.10.4 documentation @@ -21,10 +21,10 @@ - - - - + + + + @@ -61,7 +61,7 @@
                      - 5.6.2 + 5.10.4
                      @@ -187,7 +187,7 @@

                      - © Copyright 2019, Stefano Pigozzi + © Copyright 2020, Stefano Pigozzi

                      diff --git a/docs/html/packs/newpack.html b/docs/html/packs/newpack.html index 1a345b95..15f610ab 100644 --- a/docs/html/packs/newpack.html +++ b/docs/html/packs/newpack.html @@ -8,7 +8,7 @@ - Creating a new Pack — Royalnet 5.6.2 documentation + Creating a new Pack — Royalnet 5.10.4 documentation @@ -21,10 +21,10 @@ - - - - + + + + @@ -61,7 +61,7 @@
                      - 5.6.2 + 5.10.4
                      @@ -92,7 +92,6 @@
                    • Creating the repository
                    • Adding new dependencies to the Pack
                    • +
                    • Updating the dependencies
                    • +
                    • The README.md file
                    • Publishing the pack
                  • @@ -183,14 +184,14 @@

                    Creating a new PackΒΆ

                    PrerequisitesΒΆ

                    -

                    You’ll need to have Python 3.8 and poetry +

                    You’ll need to have Python 3.8 and poetry to develop Royalnet Packs.

                    Creating the repositoryΒΆ

                    To create a new pack, create a new repository based on the Royalnet Pack template and clone it to your workspace.

                    -

                    After cloning the template, run poetry install to install the dependencies for the pack, creating the poetry.lock file.

                    +

                    After cloning the template, run poetry install to create a virtualenv and install the dependencies for the pack in it.

                    pyproject.tomlΒΆ

                    The pyproject.toml file contains information about your Python project that will be read by poetry while building @@ -207,24 +208,14 @@ the pack and publishing it to PyPI.

                    examplepackΒΆ

                    The examplepack folder contains the source code of your pack, and should be renamed to the name you set in the pyproject.toml file.

                    -

                    It should contain a version.py file and six folders:

                    +

                    It should contain six folders:

                    examplepack
                     β”œβ”€β”€ commands
                     β”œβ”€β”€ events
                     β”œβ”€β”€ stars
                     β”œβ”€β”€ tables
                     β”œβ”€β”€ types
                    -β”œβ”€β”€ utils
                    -└── version.py
                    -
                    -
                    -
                    -
                    -

                    version.pyΒΆ

                    -

                    The version.py file contains the version number of your pack.

                    -

                    If you changed the version field in the pyproject.toml file, change the value of semantic in version.py to the same value.

                    -

                    Remember to use semantic versioning!

                    -
                    semantic = "1.0.0"
                    +└── utils
                     
                    @@ -264,13 +255,22 @@ the pack and publishing it to PyPI.

                    Adding new dependencies to the PackΒΆ

                    -

                    As the Pack is actually a Python package, you can use poetry (or pip) to add new dependencies!

                    +

                    As the Pack is actually a Python package, you can use poetry to add new dependencies!

                    Use poetry add packagename to add and install a new dependency from the PyPI.

                    +
                    +

                    Updating the dependenciesΒΆ

                    +

                    You can update all your dependencies by using: poetry update.

                    +
                    +
                    +

                    The README.md fileΒΆ

                    +

                    The README.md file is the first thing that users of your pack will see!

                    +

                    It’s recommended to describe accurately how to install and configure the pack, so other users will be able to use it too!

                    +

                    Publishing the packΒΆ

                    -

                    To publish your Pack on the PyPI, run poetry build, then poetry publish.

                    -

                    Poetry will build your Pack and upload it to the PyPI for you.

                    +

                    To publish your Pack on the PyPI, run poetry publish --build.

                    +

                    Poetry will build your Pack and upload it to the PyPI for you!

                    @@ -294,7 +294,7 @@ the pack and publishing it to PyPI.

                    - © Copyright 2019, Stefano Pigozzi + © Copyright 2020, Stefano Pigozzi

                    diff --git a/docs/html/packs/pack.html b/docs/html/packs/pack.html index 8b4f24ba..5d60b410 100644 --- a/docs/html/packs/pack.html +++ b/docs/html/packs/pack.html @@ -8,7 +8,7 @@ - Royalnet Packs — Royalnet 5.6.2 documentation + Royalnet Packs — Royalnet 5.10.4 documentation @@ -21,10 +21,10 @@ - - - - + + + + @@ -61,7 +61,7 @@
                    - 5.6.2 + 5.10.4
                    @@ -195,7 +195,7 @@ Royalnet instance to add more features, such as more commands, more webpages and

                    - © Copyright 2019, Stefano Pigozzi + © Copyright 2020, Stefano Pigozzi

                    diff --git a/docs/html/packs/star.html b/docs/html/packs/star.html index e26aeb52..ed66a48f 100644 --- a/docs/html/packs/star.html +++ b/docs/html/packs/star.html @@ -8,7 +8,7 @@ - Adding a Star to the Pack — Royalnet 5.6.2 documentation + Adding a Star to the Pack — Royalnet 5.10.4 documentation @@ -21,10 +21,10 @@ - - - - + + + + @@ -61,7 +61,7 @@
                    - 5.6.2 + 5.10.4
                    @@ -187,7 +187,7 @@

                    - © Copyright 2019, Stefano Pigozzi + © Copyright 2020, Stefano Pigozzi

                    diff --git a/docs/html/packs/table.html b/docs/html/packs/table.html index 33d9fc12..dcd9d128 100644 --- a/docs/html/packs/table.html +++ b/docs/html/packs/table.html @@ -8,7 +8,7 @@ - Using Tables and databases — Royalnet 5.6.2 documentation + Using Tables and databases — Royalnet 5.10.4 documentation @@ -21,10 +21,10 @@ - - - - + + + + @@ -61,7 +61,7 @@
                    - 5.6.2 + 5.10.4
                    @@ -187,7 +187,7 @@

                    - © Copyright 2019, Stefano Pigozzi + © Copyright 2020, Stefano Pigozzi

                    diff --git a/docs/html/py-modindex.html b/docs/html/py-modindex.html index 1d262628..9604d0ec 100644 --- a/docs/html/py-modindex.html +++ b/docs/html/py-modindex.html @@ -8,7 +8,7 @@ - Python Module Index — Royalnet 5.6.2 documentation + Python Module Index — Royalnet 5.10.4 documentation @@ -21,10 +21,10 @@ - - - - + + + + @@ -62,7 +62,7 @@
                    - 5.6.2 + 5.10.4
                    @@ -220,7 +220,7 @@

                    - © Copyright 2019, Stefano Pigozzi + © Copyright 2020, Stefano Pigozzi

                    diff --git a/docs/html/randomdiscoveries.html b/docs/html/randomdiscoveries.html index 583beaa4..e8b35423 100644 --- a/docs/html/randomdiscoveries.html +++ b/docs/html/randomdiscoveries.html @@ -8,7 +8,7 @@ - Random discoveries — Royalnet 5.6.2 documentation + Random discoveries — Royalnet 5.10.4 documentation @@ -21,10 +21,10 @@ - - - - + + + + @@ -61,7 +61,7 @@
                    - 5.6.2 + 5.10.4
                    @@ -206,7 +206,7 @@

                    - © Copyright 2019, Stefano Pigozzi + © Copyright 2020, Stefano Pigozzi

                    diff --git a/docs/html/search.html b/docs/html/search.html index 340c8e1a..bf3de807 100644 --- a/docs/html/search.html +++ b/docs/html/search.html @@ -8,7 +8,7 @@ - Search — Royalnet 5.6.2 documentation + Search — Royalnet 5.10.4 documentation @@ -21,11 +21,11 @@ - - - - - + + + + + @@ -60,7 +60,7 @@
                    - 5.6.2 + 5.10.4
                    @@ -174,7 +174,7 @@

                    - © Copyright 2019, Stefano Pigozzi + © Copyright 2020, Stefano Pigozzi

                    diff --git a/docs/html/searchindex.js b/docs/html/searchindex.js index 983ff9ea..e45d8a00 100644 --- a/docs/html/searchindex.js +++ b/docs/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["apireference","index","packs/command","packs/event","packs/newpack","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\\newpack.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,""],find_user:[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,""],PageStar:[0,1,1,""],Star:[0,1,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_page_stars:[0,2,1,""],run_blocking:[0,2,1,""],run_process:[0,2,1,""],running:[0,5,1,""],starlette:[0,5,1,""],stars:[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,4],"default":[0,2],"enum":4,"final":[0,2],"function":[0,2,4],"ges\u00f9":0,"import":[0,2,4],"int":0,"new":[0,5],"null":0,"return":[0,2],"short":[0,2],"static":0,"super":2,"true":[0,2],"try":[0,2],"while":[0,2,4,8],Adding:5,For:2,Has:0,Its:4,Not:[0,2],That:0,The:[0,2],Then:2,There:2,These:2,Use:[2,4],Using:[4,5],__all__:2,__getitem__:0,__init__:[0,2],__name__:2,_gone:0,abl:0,about:[0,2,4],abstracteventloop:0,access:0,accur:0,acquir:0,actual:4,add:[0,2,4,5],added:0,adding:[0,2],addit:0,addition:2,address:0,admin:0,after:4,aio:0,akin:0,alchemi:4,alchemy_cfg:0,alchemy_easi:0,alchemy_hard:0,alchemyexcept:0,aldent:0,alia:0,alias:0,all:[0,2,4],allow:[0,2],also:0,altern:0,alwai:2,amount:0,andformat:0,ani:[0,2],anoth:0,anymor:0,anystr:0,anyth:4,anywher:4,aopen:0,api:[1,2],app:0,append:2,arg:[0,2],argument:0,around:0,ascend:2,assertionerror:2,async:[0,2],asyncifi:[0,2],asyncio:[0,2],asyncron:[0,2],attribut:2,authent:8,author:0,autocomplet:0,autogener:0,automat:2,avail:[0,2],available_command:2,avoid:[0,2],await:[0,2],back:2,backpack:2,banana:2,barderror:0,base64:0,base:[0,4],becaus:[0,2],been:0,befor:[0,2,4],begin:2,being:[0,2],between:0,bind:0,blockingli:0,bool:0,bot:[0,8],both:0,brace:2,bracket:2,brief:2,broadcast:0,bug:2,build:4,call:[0,4],call_herald_ev:0,callabl:0,callback:0,can:[0,2,4,5],cannot:0,carb:2,carbonara:[0,2],cate:0,caus:[0,2],central:0,certain:2,chang:[2,4],channel:0,charact:0,chat:2,check:0,choos:4,circular:4,classmethod:0,clone:4,close:[0,2],code:[0,4],collect:0,column:2,come:2,command:5,commandarg:[0,2],commanddata:[0,2],commanderror:[0,2],commandinterfac:0,comment:4,commit:0,commun:0,comparis:2,compat:4,complet:0,complex:2,config:[0,2],configur:0,configurationerror:[0,2],connect:[0,2],connectedcli:0,connectionclosederror:0,consid:2,consol:0,constellation_cfg:0,contain:[0,2,4],content:4,context:0,convers:0,convert:0,copi:0,coroutin:0,correspond:0,could:2,creat:[0,5],curli:2,current:0,custom:4,dai:0,data:[0,2],databas:[0,4,5],database_uri:0,date:0,datetim:0,debug:2,declar:0,declarativemeta:0,def:2,default_ytdl_arg:0,delet:0,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:[4,8],dict:0,dictionari:0,did:[0,2],differ:[0,2],directli:[0,2],discord:0,discordserf:0,discoveri:1,displai:0,docstr:0,document:[1,2,3,6,7],doe:[0,2],doesn:0,don:[0,2],down:0,download:0,download_1_terabyte_of_spaghetti:2,download_fil:0,dure:0,each:0,easili:0,edit:4,effect:2,either:0,element:[0,2],ellipsi:0,els:[2,4],emoji:[0,2],enabl:0,encod:0,encount:0,end:0,ending_t:0,engin:[0,2],english:0,enough:[0,2],enter:2,epic:0,epoch:0,equival:2,error:0,error_if_non:0,error_if_unavail:[0,2],establish:2,even:0,event:[0,5],event_nam:0,everi:[0,2],everyth:4,exact:[0,2],exampl:[0,2],exc:0,except:[0,2],exceptionstar:0,exclus:0,execut:[0,2],executor:0,exist:[0,2],exit:0,expect:2,explain:0,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,8],fals:0,featur:[0,5],fetch:0,ffmpeg_python:0,field:[2,4],file:[0,2,4],filenam:0,find:0,find_client:0,find_destin:0,find_us:0,finish:[2,4],first:[0,2],flag:0,follow:4,forbidden:4,form:0,format:0,found:[0,2,8],friendli:0,from:[0,2,4],from_config:0,from_dict:0,from_json_byt:0,from_json_str:0,from_url:0,from_urluuid:0,gener:[0,2],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,4],heartbeat:8,herald_cfg:0,herald_task:0,heralderror:0,here:[2,4,8],how:[0,4],howev:4,http:0,ident:0,identifi:0,identity_chain:0,identity_t:0,ignor:2,ignoreerror:0,imag:2,implement:0,includ:[2,4],index:[0,1],info:0,inform:4,inherit:[0,2],init_alchemi:0,init_herald:0,init_log:0,init_sentri:0,initi:0,input:0,insert:4,insid:[0,2],instal:[0,4],instanc:[0,2,4,5],instanti:0,instead:[0,2],instruct:[0,4],interact:2,interfac:[0,2],interface_factori:0,interface_nam:0,internet:0,invalid:[0,2],invalidinputerror:[0,2],invalidserverresponseerror:0,invok:0,is_download:0,is_open:2,isn:[0,2],itali:2,item:0,iter:0,its:[0,4],join:[0,2],json:0,jsonabl:0,just:2,kappa:0,keep:2,kei:0,keyboard:0,keyboardkei:0,keyword:[0,2],kind:2,kitchen:2,kwarg:0,last:0,later:2,learn:4,less:0,level:0,like:[0,2],line:0,link:0,link_typ:0,linkerror:0,list:[0,2],listen:0,load:5,lock:[0,4],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,metadata:2,method:[0,2],middl:0,minimum:[0,2],misconfigur:2,miss:[0,2],mistak:2,modul:[0,4,5],month:0,more:[0,5],msg_type:0,multilock:0,multipl:[0,2],multiplefileserror:0,multiprocess:0,music:0,must:0,name:[0,2,4],need:4,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],note:4,notfounderror:0,noth:[0,2],notic:2,notimpl:0,now:2,number:[0,2,4],numberemojiformat:0,numer:0,object:[0,2],onc:[0,2],one:[0,2],one_or_non:2,ones:2,onli:[0,2],open:[0,2],option:0,optional_arg:0,order_bi:2,ordin:0,ordinal_numer:0,ordinalformat:0,org:0,orm:[0,2],other:[0,2,4],otherwis:2,out:8,outtmpl:0,over:0,overrid:[0,2],pack:[0,1,2],pack_cfg:0,packag:[0,4],packagenam:4,packet:0,packs_cfg:0,page:[0,4],page_star:0,pagestar:[0,4],paltri:0,paramet:[0,2],part:[0,2],particularli:0,pass:[0,2],pasta:0,pastapack:4,path:0,pattern:[0,2],ping:2,pingcommand:2,pip:[0,4],plai:2,pleas:[0,4],poetri:4,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,4],process:0,program:[0,2],programerror:[0,2],project:[0,4],properli:0,properti:[0,4],proto:0,provid:0,psycopg2:0,put:0,pypi:[0,4],python:[2,4,5],quiet:0,rais:0,random:[0,1],rbt:2,read:[0,2,4],reason:8,receiv:[0,2],reciev:0,recogn:2,recreat:0,refer:[1,2],regex:0,regist:0,register_command:0,register_ev:0,register_page_star:0,relat:0,relationship:0,rememb:[2,4],remot:[0,4],renam:4,render:0,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,4],result:0,retriev:0,retrieve_for_url:0,retrieve_info:0,right_now:2,role:2,rout:[0,4],route_packag:0,row:[0,2],royalnet:[0,2,4],run:[0,4],run_block:0,run_process:0,safe:0,same:[2,4],schema:0,script:2,search:0,second:0,secret:0,section:[0,2,3,6,7],secur:0,select:0,self:[0,2],semant:4,send:[0,2],sensei:0,sent:[0,2],sentri:0,sentry_cfg:0,sentry_exc:0,separ:[0,2],sequenc:0,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,4],set_ytdlinfo_from_id3_tag:0,shortcut:0,should:[0,2,4],shouldn:0,side:2,simpl:2,simultan:0,singl:[0,2],six:4,sleep:[0,2],sleep_until:0,small:[0,2],some:[0,2,8],someth:[0,2],somewher:0,soon:0,sort:2,sourc:[0,4],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,4],sqlalchemi:[0,2,4],squar:2,stai:[0,2],star:[0,5],starlett:0,start:[0,2],starting_t:0,statement:0,steffo:0,stop:[2,8],str:[0,2],string:0,stuff:0,subclass:2,submodul:0,subpackag:0,success:0,support:[0,2],syntax:[0,2],tabl:[0,2,5],table_df:0,tablenotfounderror:0,take:2,target:0,task:0,telegram:[0,2],telegramserf:0,tell:2,templat:4,text:0,than:[0,2],thei:[0,2],them:[0,2,4],thi:[0,2,3,4,6,7],thing:[2,8],think:2,thought:2,thread:0,through:[0,2],time:[0,2,8],titl:0,to_dict:0,to_json_byt:0,to_json_str:0,to_urluuid:0,todo:4,toml:2,too:[0,2,4],tool:4,tri:2,trigger:0,tupl:[0,2],two:0,type:[0,2],unavail:2,underscor:0,undescrib:0,unexpectedli:0,unicod:0,union:[0,2],unsupportederror:[0,2],until:[0,2],upload:4,uri:0,url:0,use:[0,2,4],used:[0,2,4],useful:0,user:[0,2],usererror:[0,2],usernam:2,uses:[0,4],using:[0,2],usual:0,utf:0,util:2,uuid:0,uvicorn:0,valu:[0,2,4],video:0,viktya:0,wai:2,want:[0,2],wasn:[0,2],web:0,webpag:[0,5],webserv:[0,4],websit:[0,2],websocket:0,websocketserverprotocol:0,weird:0,welcom:1,went:0,were:[0,2,8],what:[0,2],when:[0,2],whenev:2,where:0,which:[0,2],wiki:0,wikipedia:0,without:4,won:[0,2],word:0,work:[0,2],workspac:4,wrap:2,wrapper:0,wrong:[0,2],wrong_____:0,year:0,yet:[0,2,3,6,7],you:[0,2,4],your:[2,4],youtube_dl:0,youtubedl:0,ytdl_arg:0,ytdldateformat:0,ytdlerror:0,ytdlfile:0,ytdlinfo:0,yyyi:0,yyyymmdd:0},titles:["API Reference","Royalnet","Creating a new Command","Using Events","Creating a new Pack","Royalnet Packs","Adding a Star to the Pack","Using Tables and databases","Random discoveries"],titleterms:{"new":[2,4],Adding:[2,4,6],The:4,Using:[2,3,7],access:2,alchemi:[0,2],api:0,argument:2,backpack:0,bard:0,bot:2,call:2,code:[2,8],command:[0,2,4],constel:0,coroutin:2,creat:[2,4],databas:[2,7],delet:2,depend:4,direct:2,discord:8,discoveri:8,displai:2,error:[2,8],event:[2,3,4],examplepack:4,express:2,fetch:2,filter:2,folder:4,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:[4,5,6],prerequisit:4,publish:4,pyproject:4,queri:2,rais:2,random:8,refer:0,regular:2,repeat:2,repositori:4,result:2,royalnet:[1,5],run:2,serf:0,slow:2,some:1,star:[4,6],string:2,tabl:[4,7],toml:4,type:4,undocu:8,useful:1,util:[0,4],version:4,websocket:8}}) \ No newline at end of file +Search.setIndex({docnames:["apireference","index","packs/command","packs/event","packs/newpack","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.index":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/newpack.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,""],ConfigDict:[0,1,1,""],ConfigurationError:[0,3,1,""],ExternalError:[0,3,1,""],HeraldEvent:[0,1,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,""],description:[0,5,1,""],loop:[0,2,1,""],name:[0,5,1,""],run:[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,""],find_user:[0,2,1,""],get_author:[0,2,1,""],keyboard:[0,2,1,""],loop:[0,2,1,""],register_keyboard_key:[0,2,1,""],reply:[0,2,1,""],reply_image:[0,2,1,""],session:[0,2,1,""],session_close:[0,2,1,""],session_commit:[0,2,1,""],unregister_keyboard_key:[0,2,1,""]},"royalnet.commands.ConfigDict":{convert:[0,2,1,""]},"royalnet.commands.HeraldEvent":{alchemy:[0,2,1,""],loop:[0,2,1,""],name:[0,5,1,""],run:[0,2,1,""]},"royalnet.commands.KeyboardKey":{press:[0,2,1,""]},"royalnet.constellation":{Constellation:[0,1,1,""],PageStar:[0,1,1,""],Star:[0,1,1,""]},"royalnet.constellation.Constellation":{address:[0,5,1,""],alchemy:[0,5,1,""],call_herald_event:[0,2,1,""],events:[0,5,1,""],herald:[0,5,1,""],herald_task:[0,5,1,""],init_herald:[0,2,1,""],loop:[0,5,1,""],network_handler:[0,2,1,""],port:[0,5,1,""],register_events:[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,""],stars:[0,5,1,""]},"royalnet.constellation.PageStar":{methods:[0,2,1,""],path:[0,5,1,""]},"royalnet.constellation.Star":{Session:[0,2,1,""],alchemy:[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":{alchemy:[0,5,1,""],call:[0,2,1,""],call_herald_event:[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_name:[0,5,1,""],loop:[0,5,1,""],master_table:[0,5,1,""],network_handler:[0,2,1,""],prefix:[0,5,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_async_wrap:[0,4,1,""],sentry_exc:[0,4,1,""],sentry_wrap:[0,4,1,""],sleep_until:[0,4,1,""],strip_tabs:[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,4],"default":[0,2],"enum":4,"final":[0,2],"function":[0,2,4],"ges\u00f9":0,"import":[0,2,4],"int":0,"new":[0,5],"null":0,"return":[0,2],"short":[0,2],"static":0,"super":2,"true":[0,2],"try":[0,2],"while":[0,2,4,8],Adding:5,For:2,Has:0,Its:4,Not:[0,2],That:0,The:[0,2],Then:2,There:2,These:2,Use:[2,4],Using:[4,5],__all__:2,__getitem__:0,__init__:[0,2],__name__:2,__total_spaghetti:2,abl:[0,4],about:[0,2,4],abstracteventloop:0,accept:2,access:0,accur:[0,4],acquir:0,across:2,actual:4,add:[0,2,4,5],added:0,adding:[0,2],addit:0,addition:2,address:0,admin:0,after:4,aio:0,akin:0,al_dent:2,alchemi:4,alchemy_cfg:0,alchemy_easi:0,alchemy_hard:0,alchemyexcept:0,aldent:0,alia:0,alias:0,all:[0,2,4],allow:[0,2],also:[0,2],altern:0,alwai:2,amount:0,andformat:0,ani:[0,2],anoth:0,answer:2,anymor:0,anystr:0,anyth:4,anywher:4,aopen:0,api:[1,2],app:0,append:2,arg:[0,2],argument:0,around:0,ascend:2,assertionerror:2,assum:2,async:[0,2],asynccontextmanag:2,asyncifi:[0,2],asyncio:[0,2],asynciter:0,asyncron:[0,2],attach:[0,2],attribut:2,authent:8,author:0,autocomplet:0,autogener:0,automat:2,avail:0,available_command:2,avoid:[0,2],await:[0,2],back:2,backpack:2,banana:2,barderror:0,base64:0,base:[0,4],bbcode:2,becaus:[0,2],been:[0,2],befor:[0,2,4],begin:2,being:[0,2],between:0,bind:0,block:0,blockingli:0,bold:2,bool:0,bot:[0,2,8],both:0,brace:2,bracket:2,brief:2,broadcast:0,bug:2,build:4,button:2,call:[0,4],call_herald_ev:[0,2],callabl:0,callback:0,can:[0,2,4,5],cannot:[0,2],caption:0,carb:2,carbonara:[0,2],cate:0,caus:[0,2],central:0,certain:2,chang:2,channel:0,charact:0,chat:2,check:[0,2],choos:4,circular:4,classmethod:0,clone:4,close:[0,2],code:[0,4],coll:0,collect:0,column:2,com:2,come:2,command:5,commandarg:[0,2],commanddata:[0,2],commanderror:[0,2],comment:[2,4],commit:0,commun:0,comparis:2,compat:4,complet:0,complex:2,config:[0,2],configdict:0,configur:[0,4],configurationerror:[0,2],connect:[0,2],connectedcli:0,connectionclosederror:0,consid:2,constellation_cfg:0,contain:[0,2,4],content:[2,4],context:[0,2],contextmanag:2,convers:0,convert:0,copi:0,coroutin:0,correspond:0,could:2,counterpart:0,creat:[0,5],create_task:2,curli:2,current:0,custom:4,dai:0,data:0,databas:[0,4,5],database_uri:0,date:0,datetim:0,debug:2,declar:0,declarativemeta:0,def:2,default_ytdl_arg:0,delet:0,delete_asap:0,delete_invok:[0,2],dent:2,depend:2,depth:0,desc:2,descend:2,describ:[2,4],descript:[0,2],destin:0,destination_conv_id:0,detail:0,develop:[4,8],dict:0,dictionari:0,did:[0,2],differ:[0,2],directli:[0,2],discord:[0,2],discordserf:2,discoveri:1,displai:0,docstr:0,document:[1,3,6,7],doe:[0,2],doesn:0,don:[0,2],down:0,download:0,download_1_terabyte_of_spaghetti:2,download_fil:0,drive:2,due:2,dure:[0,2],each:0,easili:0,edit:4,effect:2,either:0,eject:2,element:[0,2],elif:2,ellipsi:0,els:[2,4],emoji:[0,2],enabl:0,encod:0,encount:0,end:0,ending_t:0,engin:[0,2],english:0,enough:[0,2],enter:2,epic:0,epoch:0,equival:2,error:0,error_if_non:0,error_if_unavail:[0,2],establish:2,even:0,event:[0,5],event_nam:[0,2],everi:[0,2],everyth:[2,4],exact:[0,2],exampl:[0,2],exc:0,except:[0,2],exceptionstar:0,exclus:0,execut:[0,2],executor:0,exist:[0,2],exit:0,expect:2,explain:0,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,8],fals:0,famm:2,featur:[0,5],fetch:0,ffmpeg_python:0,field:[2,4],file:0,filenam:0,find:0,find_client:0,find_destin:0,find_us:0,finish:[2,4],first:[0,2,4],first_pasta:2,flag:[0,2],floppi:2,follow:4,forbidden:4,form:0,format:0,found:[0,2,8],free:2,friendli:0,from:[0,4],from_config:0,from_dict:0,from_json_byt:0,from_json_str:0,from_url:0,from_urluuid:0,further:2,gener:[0,2],get:[0,2],get_author:0,github:1,going:0,gone:0,googl:2,greater:2,group:[0,2],handl:0,handler:0,happen:0,has:[0,2],has_info:0,have:[0,2,4],heartbeat:8,herald_cfg:0,herald_task:0,heralderror:0,heraldev:0,here:[2,4,8],how:[0,4],howev:4,http:[0,2],ident:0,identifi:0,identity_chain:0,identity_t:0,ignor:2,ignoreerror:0,imag:[0,2],implement:0,includ:[2,4],independ:2,index:[0,1],info:0,inform:4,inherit:[0,2],init_alchemi:0,init_herald:0,init_log:0,init_sentri:0,initi:[0,2],inlin:2,input:0,insert:4,insid:[0,2],instal:[0,4],instanc:[0,2,4,5],instanti:0,instead:[0,2],instruct:[0,4],interact:2,interfac:[0,2],interface_nam:0,internet:0,invalid:[0,2],invalidinputerror:[0,2],invalidserverresponseerror:0,invok:0,iobas:0,is_download:0,is_open:2,isinst:2,isn:[0,2],ital:2,itali:2,item:0,iter:0,its:[0,2,4],join:[0,2],json:0,jsonabl:0,just:2,kappa:0,keep:2,kei:[0,2],keyboard:0,keyboardkei:[0,2],keyword:[0,2],kind:2,kitchen:2,kwarg:[0,2],last:0,later:2,le_epic_prank__gone_wrong_____:0,learn:4,less:0,level:0,like:[0,2],line:0,link:[0,2],link_typ:0,linkerror:0,list:[0,2],listen:0,load:5,lock:0,log:0,logging_cfg:0,look:2,loop:[0,2],made:[0,2],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,metadata:2,method:[0,2],middl:0,might:0,minimum:[0,2],miss:[0,2],mistak:2,mode:2,modul:[0,4,5],month:0,more:[0,5],msg_type:0,multilin:2,multilock:0,multipl:0,multiplefileserror:0,multiprocess:0,music:0,must:0,mycoroutin:2,name:[0,2,4],need:[2,4],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],note:4,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],onlin:2,open:[0,2],option:0,optional_arg:0,order_bi:2,ordin:0,ordinal_numer:0,ordinalformat:0,org:0,orm:[0,2],other:[0,2,4],otherwis:2,out:8,outtmpl:0,over:0,overrid:[0,2],own:2,pack:[0,1,2],pack_cfg:0,packag:[0,4],packagenam:4,packet:0,packs_cfg:0,page:[0,4],page_star:0,pagestar:[0,4],paltri:0,paramet:[0,2],parent:0,part:[0,2],particularli:0,pass:[0,2],pasta:0,pastapack:4,path:0,pattern:[0,2],persist:2,persistent_vari:2,ping:2,pingcommand:2,pip:0,plai:2,pleas:[0,4],poetri:4,pong:2,port:0,possibl:[0,2],post:0,postgresql:2,prank:0,prefix:0,prepar:0,present:2,press:[0,2],previou:2,previous:2,print:2,privat:2,probabl:0,problem:[0,2],procedur:[0,4],process:0,program:[0,2],programerror:[0,2],project:[0,4],properli:0,properti:[0,4],protect:2,proto:0,provid:0,psycopg2:0,put:0,pypi:[0,4],python:[2,4,5],quiet:0,rais:0,random:[0,1],rbt:2,read:[0,4],reason:8,receiv:[0,2],reciev:0,recogn:2,recommend:[2,4],recreat:0,refer:[1,2],regex:0,regist:0,register_command:0,register_ev:0,register_keyboard_kei:0,register_page_star:0,relat:0,relationship:0,rememb:2,remot:[0,4],renam:4,render:[0,2],replac:0,repli:0,reply_imag:0,report:0,repres:[0,2],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,4],restart:2,result:0,retriev:0,retrieve_for_url:0,retrieve_info:0,right_now:2,role:2,rout:[0,4],route_packag:0,row:[0,2],royalherald:0,royalnet:[0,2,4],rsd:2,rst:2,run:[0,4],run_block:0,run_process:0,safe:0,same:2,schedul:2,schema:0,scope:2,screen:2,script:2,search:0,second:[0,2],second_pasta:2,secret:0,section:[2,3,6,7],secur:0,see:[2,4],select:0,self:[0,2],send:[0,2],sensei:0,sent:[0,2],sentri:0,sentry_async_wrap:0,sentry_cfg:0,sentry_exc:0,sentry_wrap:0,separ:[0,2],sequenc:0,serferror:0,serv:[0,2],server:[0,2],servererror:0,servic:2,session:[0,2],session_acm:0,session_clos:0,session_cm:0,session_commit:0,set:[0,2,4],set_ytdlinfo_from_id3_tag:0,shortcut:0,should:[0,2,4],shouldn:0,side:2,significantli:0,simpl:2,simultan:0,singl:[0,2],six:4,sleep:[0,2],sleep_until:0,slower:0,small:[0,2],some:[0,2,8],someth:[0,2],somewher:0,soon:0,sort:2,sourc:[0,4],source_conv_id:0,space:[0,2],spaggia:0,spaghetti:[0,2],spaghetticommand:2,spaghettipack:2,spaghi:2,special:2,specif:[0,2],specifi:[0,2],sphinx:0,sql:[0,4],sqlalchemi:[0,2,4],squar:2,stai:[0,2],star:[0,5],starlett:0,start:0,starting_t:0,statement:0,steffo:0,step:2,stop:[2,8],store:2,str:[0,2],string:0,strip_tab:0,stuff:0,subclass:2,submodul:0,subpackag:0,subset:2,success:0,support:[0,2],syntax:[0,2],tabl:[0,2,5],table_df:0,tablenotfounderror:0,take:2,target:0,task:[0,2],telegram:[0,2],telegramserf:2,tell:2,templat:4,text:[0,2],than:[0,2],thei:[0,2],them:[0,2,4],thi:[0,2,3,4,6,7],thing:[2,4,8],think:2,thought:2,thread:0,through:[0,2],time:[0,2,8],titl:0,to_dict:0,to_json_byt:0,to_json_str:0,to_urluuid:0,todo:4,toml:2,too:[0,2,4],tool:4,top:2,tri:2,trigger:0,tupl:[0,2],two:[0,2],type:[0,2],unavail:2,underscor:0,undescrib:0,unexpectedli:0,unicod:0,union:[0,2],unregister_keyboard_kei:0,unsupportederror:[0,2],until:[0,2],upload:4,uri:0,url:[0,2],use:[0,2,4],used:[0,2,4],useful:0,user:[0,2,4],usererror:[0,2],usernam:2,uses:[0,4],using:[0,2,4],usual:0,utf:0,util:2,uuid:0,uvicorn:0,valid:2,valu:[0,2],variabl:2,video:0,view:2,viktya:0,virtualenv:4,wai:[0,2],wait:[0,2],want:[0,2],wasn:[0,2],web:0,webpag:[0,5],webserv:[0,4],websit:[0,2],websocket:0,websocketserverprotocol:0,weird:0,welcom:1,went:0,were:[0,2,8],what:[0,2],when:[0,2],whenev:2,where:0,which:[0,2],wiki:0,wikipedia:0,without:4,won:[0,2],word:0,work:[0,2],workspac:4,would:2,wrap:2,wrapper:0,wrong:[0,2],year:0,yet:[0,3,6,7],you:[0,2,4],your:[2,4],youtube_dl:0,youtubedl:0,ytdl_arg:0,ytdldateformat:0,ytdlerror:0,ytdlfile:0,ytdlinfo:0,yyyi:0,yyyymmdd:0},titles:["API Reference","Royalnet","Creating a new Command","Using Events","Creating a new Pack","Royalnet Packs","Adding a Star to the Pack","Using Tables and databases","Random discoveries"],titleterms:{"new":[2,4],Adding:[2,4,6],The:4,Using:[2,3,7],access:2,alchemi:[0,2],api:0,argument:2,avail:2,backpack:0,bard:0,between:2,call:2,callback:2,code:[2,8],command:[0,2,4],configur:2,constel:0,coroutin:2,creat:[2,4],data:2,databas:[2,7],delet:2,depend:4,direct:2,discord:8,discoveri:8,displai:2,distinguish:2,error:[2,8],event:[2,3,4],examplepack:4,express:2,fetch:2,file:[2,4],filter:2,folder:4,format:2,from:2,full:2,herald:0,invok:2,job:2,keyboard:2,link:1,messag:2,more:2,multipl:2,oper:2,option:2,order:2,pack:[4,5,6],platform:2,prerequisit:4,publish:4,pyproject:4,queri:2,rais:2,random:8,read:2,readm:4,refer:0,regular:2,repeat:2,repli:2,repositori:4,result:2,royalnet:[1,5],run:2,serf:[0,2],share:2,slow:2,some:1,star:[4,6],start:2,string:2,tabl:[4,7],tag:2,toml:4,type:4,undocu:8,updat:4,useful:1,util:[0,4],websocket:8}}) \ No newline at end of file diff --git a/docs_source/conf.py b/docs_source/conf.py index 84027802..60596c2e 100644 --- a/docs_source/conf.py +++ b/docs_source/conf.py @@ -20,7 +20,7 @@ import royalnet project = 'Royalnet' # noinspection PyShadowingBuiltins -copyright = '2019, Stefano Pigozzi' +copyright = '2020, Stefano Pigozzi' author = 'Stefano Pigozzi' version = royalnet.__version__ release = royalnet.__version__ diff --git a/docs_source/packs/command.rst b/docs_source/packs/command.rst index 97e2cfc0..09766f2f 100644 --- a/docs_source/packs/command.rst +++ b/docs_source/packs/command.rst @@ -3,7 +3,7 @@ Creating a new Command ==================================== -A Royalnet Command is a small script that is run whenever a specific message is sent to a Royalnet interface. +A Royalnet Command is a small script that is run whenever a specific message is sent to a Royalnet platform. A Command code looks like this: :: @@ -14,12 +14,12 @@ A Command code looks like this: :: description = "Play ping-pong with the bot." + # This code is run just once, while the bot is starting def __init__(self, serf: "Serf", config): - # This code is run just once, while the bot is starting super().__init__(serf=serf, config=config) + # This code is run every time the command is called 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 @@ -32,7 +32,7 @@ Try to keep the name as short as possible, while staying specific enough so no o 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: :: +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 @@ -48,9 +48,9 @@ Inside the class, override the attributes ``name`` and ``description`` with resp 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. +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: :: +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 @@ -79,7 +79,7 @@ command to the ``available_commands`` list: :: Formatting command replies ------------------------------------ -You can use a subset of [BBCode](https://en.wikipedia.org/wiki/BBCode) to format messages sent with :meth:`CommandData.reply`: :: +You can use a subset of `BBCode `_ to format messages sent with :meth:`~CommandData.reply`: :: async def run(self, args: rc.CommandArgs, data: rc.CommandData): await data.reply("[b]Bold of you to assume that my code has no bugs.[/b]") @@ -101,7 +101,7 @@ 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` +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 @@ -148,7 +148,7 @@ If you request an argument with a certain number, but the argument does not exis Optional access ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you don't want arguments to be required, you can access them through the :meth:`CommandArgs.optional` method: it +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) @@ -168,7 +168,7 @@ You can specify a default result too, so that the method will return it instead Full string ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you want the full argument string, you can use the :meth:`CommandArgs.joined` method. :: +If you want the full argument string, you can use the :meth:`~CommandArgs.joined` method. :: args.joined() # "carbonara al-dente" @@ -184,7 +184,7 @@ 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, +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. :: @@ -204,12 +204,12 @@ To match a pattern, :func:`re.match` is used, meaning that Python will try to ma 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 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: +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. @@ -219,7 +219,7 @@ There are some subclasses of :exc:`.CommandError` that can be used for some more *Additionally displays the command syntax in the error message.* :exc:`.UnsupportedError` - The command is not supported on the interface it is being called. + The command is not supported on the platform it is being called. :exc:`.ConfigurationError` A value is missing or invalid in the ``config.toml`` section of your pack. @@ -235,7 +235,7 @@ 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 +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 @@ -266,13 +266,13 @@ 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` +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 +Not all platforms support deleting messages; by default, if the platform 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 @@ -289,7 +289,7 @@ to True: :: Sharing data between multiple calls ------------------------------------ -The :class:`Command` class is shared between multiple command calls: if you need to store some data, you may store it as a protected/private field of your command class: :: +The :class:`~Command` class is shared between multiple command calls: if you need to store some data, you may store it as a protected/private field of your command class: :: class SpaghettiCommand(rc.Command): name = "spaghetti" @@ -393,7 +393,7 @@ You can **call an event** from inside a command, and receive its return value. This may be used for example to get data from a different platform, such as getting the users online in a specific Discord server. -You can call an event with the :meth:`Serf.call_herald_event` method: :: +You can call an event with the :meth:`.Serf.call_herald_event` method: :: result = await self.serf.call_herald_event("event_name") @@ -401,19 +401,103 @@ You can also pass parameters to the called event: :: result = await self.serf.call_herald_event("event_name", ..., kwarg=..., *..., **...) -Errors raised by the event will also be raised by the :meth:`Serf.call_herald_event` method as one of the exceptions described in the :ref:`Raising errors` section. +Errors raised by the event will also be raised by the :meth:`.Serf.call_herald_event` method as one of the exceptions described in the :ref:`Raising errors` section. + +Distinguish between platforms +------------------------------------ + +To see if a command is being run on a specific platform, you can check the type of the ``self.serf`` object: :: + + import royalnet.serf.telegram as rst + import royalnet.serf.discord as rsd + ... + + if isinstance(self.serf, rst.TelegramSerf): + await data.reply("This command is being run on Telegram.") + elif isinstance(self.serf, rsd.DiscordSerf): + await data.reply("This command is being run on Discord.") + ... Displaying Keyboards ------------------------------------ -This section is not documented yet. +A keyboard is a message with multiple buttons ("keys") attached which can be pressed by an user viewing the message. -Running code at the initialization of the bot +Once a button is pressed, a callback function is run, which has its own :class:`~CommandData` context and can do everything a regular comment call could. + +The callback function is a coroutine accepting a single ``data: CommandData`` argument: :: + + async def answer(data: CommandData) -> None: + await data.reply("Spaghetti were ejected from your floppy drive!") + +To create a new key, you can use the :class:`~KeyboardKey` class: :: + + key = KeyboardKey( + short="⏏️", # An emoji representing the key on platforms the full message cannot be displayed + text="Eject spaghetti from the floppy drive", # The text displayed on the key + callback=answer # The coroutine to call when the key is pressed. + ) + +To display a keyboard and wait for a keyboard press, you can use the :meth:`~CommandData.keyboard` asynccontextmanager. +While the contextmanager is in scope, the keyboard will be valid and it will be possible to interact with it. +Any further key pressed will be answered with an error message. :: + + async with data.keyboard(text="What kind of spaghetti would you want to order?", keys=keyboard): + # This will keep the keyboard valid for 10 seconds + await asyncio.sleep(10) + +Replies in callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Calls to :meth:`~CommandData.reply` made with the :class:`~CommandData` of a keyboard callback won't always result in a message being sent: for example, on Telegram, replies will result in a small message being displayed on the top of the screen. + +Reading data from the configuration file --------------------------------------------- -This section is not documented yet. +You can read data from your pack's configuration section through the :attr:`~Command.config` attribute: :: + + [Packs."spaghettipack"] + spaghetti = { mode="al_dente", two=true } + +:: + + await data.reply(f"Here's your spaghetti {self.config['spaghetti']['mode']}!") + +Running code on Serf start +---------------------------------------------- + +The code inside ``__init__`` is run only once, during the initialization step of the bot: :: + + def __init__(self, serf: "Serf", config): + super().__init__(serf=serf, config=config) + + # The contents of this variable will be persisted across command calls + self.persistent_variable = 0 + + # The text will be printed only if the config flag is set to something + if config["spaghetti"]["two"]: + print("Famme due spaghi!") + +.. note:: Some methods may be unavailable during the initialization of the Serf. Running repeating jobs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This section is not documented yet. +To run a job independently from the rest of the command, you can schedule the execution of a coroutine inside ``__init__``: :: + + async def mycoroutine(): + while True: + print("Free spaghetti every 60 seconds!") + await asyncio.sleep(60) + + def __init__(self, serf: "Serf", config): + super().__init__(serf=serf, config=config) + self.loop.create_task(mycoroutine()) + +As it will be executed once for every platform Royalnet is running on, you may want to run the task only on a single platform: :: + + def __init__(self, serf: "Serf", config): + super().__init__(serf=serf, config=config) + if isinstance(self.serf, rst.TelegramSerf): + self.loop.create_task(mycoroutine()) + diff --git a/royalnet/commands/command.py b/royalnet/commands/command.py index c70c78ae..5bd6f321 100644 --- a/royalnet/commands/command.py +++ b/royalnet/commands/command.py @@ -39,7 +39,7 @@ class Command(metaclass=abc.ABCMeta): return f"[c]{self.serf.prefix}{self.name}[/c]" @property - def alchemy(self) -> Alchemy: + def alchemy(self) -> "Alchemy": """A shortcut for :attr:`.interface.alchemy`.""" return self.serf.alchemy diff --git a/royalnet/constellation/star.py b/royalnet/constellation/star.py index 53ca57f5..c100ede0 100644 --- a/royalnet/constellation/star.py +++ b/royalnet/constellation/star.py @@ -28,13 +28,13 @@ class Star(metaclass=abc.ABCMeta): raise NotImplementedError() @property - def alchemy(self) -> Alchemy: + def alchemy(self) -> "Alchemy": """A shortcut for the :class:`~royalnet.alchemy.Alchemy` of the :class:`Constellation`.""" return self.constellation.alchemy # noinspection PyPep8Naming @property - def Session(self) -> sqlalchemy.orm.session.Session: + def Session(self) -> "sqlalchemy.orm.session.Session": """A shortcut for the :class:`~royalnet.alchemy.Alchemy` :class:`Session` of the :class:`Constellation`.""" return self.constellation.alchemy.Session