<p>First, think of a <codeclass="docutils literal notranslate"><spanclass="pre">name</span></code> for your command.
It’s the name your command will be called with: for example, the “spaghetti” command will be called by typing <strong>/spaghetti</strong> in chat.
Try to keep the name as short as possible, while staying specific enough so no other command will have the same name.</p>
<p>Next, create a new Python file with the <codeclass="docutils literal notranslate"><spanclass="pre">name</span></code> you have thought of.
The previously mentioned “spaghetti” command should have a file called <codeclass="docutils literal notranslate"><spanclass="pre">spaghetti.py</span></code>.</p>
<p>Then, in the first row of the file, import the <aclass="reference internal"href="../apireference.html#royalnet.commands.Command"title="royalnet.commands.Command"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">Command</span></code></a> class from royalnet, and create a new class inheriting from it:</p>
<p>Inside the class, override the attributes <codeclass="docutils literal notranslate"><spanclass="pre">name</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">description</span></code> with respectively the <strong>name of the command</strong> and a <strong>small description of what the command will do</strong>:</p>
<p>Now override the <aclass="reference internal"href="../apireference.html#royalnet.commands.Command.run"title="royalnet.commands.Command.run"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">run()</span></code></a> method, adding the code you want the bot to run when the command is called.</p>
<p>To send a message in the chat the command was called in, you can use the <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandData.reply"title="royalnet.commands.CommandData.reply"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">reply()</span></code></a> coroutine:</p>
<p>Finally, open the <codeclass="docutils literal notranslate"><spanclass="pre">commands/__init__.py</span></code> file, and import your command there, then add a reference to your imported
command to the <codeclass="docutils literal notranslate"><spanclass="pre">available_commands</span></code> list:</p>
<divclass="highlight-default notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># Imports go here!</span>
<h2>Formatting command replies<aclass="headerlink"href="#formatting-command-replies"title="Permalink to this headline">¶</a></h2>
<p>You can use a subset of <aclass="reference external"href="https://en.wikipedia.org/wiki/BBCode">BBCode</a> to format messages sent with <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandData.reply"title="royalnet.commands.CommandData.reply"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">reply()</span></code></a>:</p>
<spanclass="k">await</span><spanclass="n">data</span><spanclass="o">.</span><spanclass="n">reply</span><spanclass="p">(</span><spanclass="s2">"[b]Bold of you to assume that my code has no bugs.[/b]"</span><spanclass="p">)</span>
</pre></div>
</div>
<divclass="section"id="available-tags">
<h3>Available tags<aclass="headerlink"href="#available-tags"title="Permalink to this headline">¶</a></h3>
<p>Here’s a list of all tags that can be used:</p>
<li><p><codeclass="docutils literal notranslate"><spanclass="pre">[url=https://google.com]inline</span><spanclass="pre">link[/url]</span></code> (will be rendered differently on every platform)</p></li>
<h2>Command arguments<aclass="headerlink"href="#command-arguments"title="Permalink to this headline">¶</a></h2>
<p>A command can have some arguments passed by the user: for example, on Telegram an user may type <cite>/spaghetti carbonara al-dente</cite>
to pass the <aclass="reference external"href="https://docs.python.org/3.8/library/stdtypes.html#str"title="(in Python v3.8)"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">str</span></code></a><cite>“carbonara al-dente”</cite> to the command code.</p>
<p>These arguments can be accessed in multiple ways through the <codeclass="docutils literal notranslate"><spanclass="pre">args</span></code> parameter passed to the <aclass="reference internal"href="../apireference.html#royalnet.commands.Command.run"title="royalnet.commands.Command.run"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">run()</span></code></a>
<p>If you want your command to use arguments, override the <codeclass="docutils literal notranslate"><spanclass="pre">syntax</span></code> class attribute with a brief description of the
syntax of your command, possibly using {curly braces} for required arguments and [square brackets] for optional
<spanclass="k">await</span><spanclass="n">data</span><spanclass="o">.</span><spanclass="n">reply</span><spanclass="p">(</span><spanclass="sa">f</span><spanclass="s2">"🍝 Here's your </span><spanclass="si">{</span><spanclass="n">first_pasta</span><spanclass="si">}</span><spanclass="s2">!"</span><spanclass="p">)</span>
<spanclass="k">else</span><spanclass="p">:</span>
<spanclass="k">await</span><spanclass="n">data</span><spanclass="o">.</span><spanclass="n">reply</span><spanclass="p">(</span><spanclass="sa">f</span><spanclass="s2">"🍝 Here's your </span><spanclass="si">{</span><spanclass="n">first_pasta</span><spanclass="si">}</span><spanclass="s2"> and your </span><spanclass="si">{</span><spanclass="n">second_pasta</span><spanclass="si">}</span><spanclass="s2">!"</span><spanclass="p">)</span>
<h3>Direct access<aclass="headerlink"href="#direct-access"title="Permalink to this headline">¶</a></h3>
<p>You can consider arguments as if they were separated by spaces.</p>
<p>You can then access command arguments directly by number as if the args object was a list of <aclass="reference external"href="https://docs.python.org/3.8/library/stdtypes.html#str"title="(in Python v3.8)"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">str</span></code></a>.</p>
<p>If you request an argument with a certain number, but the argument does not exist, an
<aclass="reference internal"href="../apireference.html#royalnet.commands.InvalidInputError"title="royalnet.commands.InvalidInputError"><codeclass="xref py py-exc docutils literal notranslate"><spanclass="pre">InvalidInputError</span></code></a> is raised, making the arguments accessed in this way <strong>required</strong>.</p>
<p>If you don’t want arguments to be required, you can access them through the <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandArgs.optional"title="royalnet.commands.CommandArgs.optional"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">optional()</span></code></a> method: it
will return <codeclass="docutils literal notranslate"><spanclass="pre">None</span></code> if the argument wasn’t passed, making it <strong>optional</strong>.</p>
<p>You can specify a default result too, so that the method will return it instead of returning <codeclass="docutils literal notranslate"><spanclass="pre">None</span></code>:</p>
<p>If you want the full argument string, you can use the <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandArgs.joined"title="royalnet.commands.CommandArgs.joined"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">joined()</span></code></a> method.</p>
<p>You can specify a minimum number of arguments too, so that an <aclass="reference internal"href="../apireference.html#royalnet.commands.InvalidInputError"title="royalnet.commands.InvalidInputError"><codeclass="xref py py-exc docutils literal notranslate"><spanclass="pre">InvalidInputError</span></code></a> will be
<h3>Regular expressions<aclass="headerlink"href="#regular-expressions"title="Permalink to this headline">¶</a></h3>
<p>For more complex commands, you may want to get arguments through <aclass="reference external"href="https://regexr.com/">regular expressions</a>.</p>
<p>You can then use the <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandArgs.match"title="royalnet.commands.CommandArgs.match"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">match()</span></code></a> method, which tries to match a pattern to the command argument string,
which returns a tuple of the matched groups and raises an <aclass="reference internal"href="../apireference.html#royalnet.commands.InvalidInputError"title="royalnet.commands.InvalidInputError"><codeclass="xref py py-exc docutils literal notranslate"><spanclass="pre">InvalidInputError</span></code></a> if there is no match.</p>
<p>To match a pattern, <aclass="reference external"href="https://docs.python.org/3.8/library/re.html#re.match"title="(in Python v3.8)"><codeclass="xref py py-func docutils literal notranslate"><spanclass="pre">re.match()</span></code></a> is used, meaning that Python will try to match only at the beginning of the string.</p>
<h2>Raising errors<aclass="headerlink"href="#raising-errors"title="Permalink to this headline">¶</a></h2>
<p>If you want to display an error message to the user, you can raise a <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandError"title="royalnet.commands.CommandError"><codeclass="xref py py-exc docutils literal notranslate"><spanclass="pre">CommandError</span></code></a> using the error message as argument:</p>
<spanclass="k">raise</span><spanclass="n">CommandError</span><spanclass="p">(</span><spanclass="s2">"The kitchen is closed. Come back later!"</span><spanclass="p">)</span>
</pre></div>
</div>
<p>There are some subclasses of <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandError"title="royalnet.commands.CommandError"><codeclass="xref py py-exc docutils literal notranslate"><spanclass="pre">CommandError</span></code></a> that can be used for some more specific cases:</p>
<dlclass="simple">
<dt><aclass="reference internal"href="../apireference.html#royalnet.commands.UserError"title="royalnet.commands.UserError"><codeclass="xref py py-exc docutils literal notranslate"><spanclass="pre">UserError</span></code></a></dt><dd><p>The user did something wrong, it is not a problem with the bot.</p>
</dd>
<dt><aclass="reference internal"href="../apireference.html#royalnet.commands.InvalidInputError"title="royalnet.commands.InvalidInputError"><codeclass="xref py py-exc docutils literal notranslate"><spanclass="pre">InvalidInputError</span></code></a></dt><dd><p>The arguments the user passed to the command by the user are invalid.
<dt><aclass="reference internal"href="../apireference.html#royalnet.commands.UnsupportedError"title="royalnet.commands.UnsupportedError"><codeclass="xref py py-exc docutils literal notranslate"><spanclass="pre">UnsupportedError</span></code></a></dt><dd><p>The command is not supported on the platform it is being called.</p>
<dt><aclass="reference internal"href="../apireference.html#royalnet.commands.ConfigurationError"title="royalnet.commands.ConfigurationError"><codeclass="xref py py-exc docutils literal notranslate"><spanclass="pre">ConfigurationError</span></code></a></dt><dd><p>A value is missing or invalid in the <codeclass="docutils literal notranslate"><spanclass="pre">config.toml</span></code> section of your pack.</p>
<dt><aclass="reference internal"href="../apireference.html#royalnet.commands.ExternalError"title="royalnet.commands.ExternalError"><codeclass="xref py py-exc docutils literal notranslate"><spanclass="pre">ExternalError</span></code></a></dt><dd><p>An external API the command depends on is unavailable or returned an error.</p>
</dd>
<dt><aclass="reference internal"href="../apireference.html#royalnet.commands.ProgramError"title="royalnet.commands.ProgramError"><codeclass="xref py py-exc docutils literal notranslate"><spanclass="pre">ProgramError</span></code></a></dt><dd><p>An error caused by a programming mistake. Equivalent to <aclass="reference external"href="https://docs.python.org/3.8/library/exceptions.html#AssertionError"title="(in Python v3.8)"><codeclass="xref py py-exc docutils literal notranslate"><spanclass="pre">AssertionError</span></code></a>, but includes a message to facilitate debugging.</p>
<h2>Coroutines and slow operations<aclass="headerlink"href="#coroutines-and-slow-operations"title="Permalink to this headline">¶</a></h2>
<p>You may have noticed that in the previous examples we used <codeclass="docutils literal notranslate"><spanclass="pre">await</span><spanclass="pre">data.reply("🍝")</span></code> instead of just <codeclass="docutils literal notranslate"><spanclass="pre">data.reply("🍝")</span></code>.</p>
<p>This is because <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandData.reply"title="royalnet.commands.CommandData.reply"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">reply()</span></code></a> 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.</p>
<p>By adding the <codeclass="docutils literal notranslate"><spanclass="pre">await</span></code> keyword before the <codeclass="docutils literal notranslate"><spanclass="pre">data.reply("🍝")</span></code>, we tell the bot that it can do other things, like
receiving new messages, while the message is being sent.</p>
<p>You should avoid running slow normal functions inside bot commands, as they will stop the bot from working until they
are finished and may cause bugs in other parts of the code!</p>
<p>If the slow function you want does not cause any side effect, you can wrap it with the <aclass="reference internal"href="../apireference.html#royalnet.utils.asyncify"title="royalnet.utils.asyncify"><codeclass="xref py py-func docutils literal notranslate"><spanclass="pre">royalnet.utils.asyncify()</span></code></a>
<p>Avoid using <aclass="reference external"href="https://docs.python.org/3.8/library/time.html#time.sleep"title="(in Python v3.8)"><codeclass="xref py py-func docutils literal notranslate"><spanclass="pre">time.sleep()</span></code></a> function, as it is considered a slow operation: use instead <aclass="reference external"href="https://docs.python.org/3.8/library/asyncio-task.html#asyncio.sleep"title="(in Python v3.8)"><codeclass="xref py py-func docutils literal notranslate"><spanclass="pre">asyncio.sleep()</span></code></a>,
a coroutine that does the same exact thing but in an asyncronous way.</p>
<h2>Delete the invoking message<aclass="headerlink"href="#delete-the-invoking-message"title="Permalink to this headline">¶</a></h2>
<p>The invoking message of a command is the message that the user sent that the bot recognized as a command; for example,
the message <codeclass="docutils literal notranslate"><spanclass="pre">/spaghetti</span><spanclass="pre">carbonara</span></code> is the invoking message for the <codeclass="docutils literal notranslate"><spanclass="pre">spaghetti</span></code> command run.</p>
<p>You can have the bot delete the invoking message for a command by calling the <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandData.delete_invoking"title="royalnet.commands.CommandData.delete_invoking"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">delete_invoking</span></code></a>
<p>You can have the method raise an error if the message can’t be deleted by setting the <codeclass="docutils literal notranslate"><spanclass="pre">error_if_unavailable</span></code> parameter
<spanclass="k">await</span><spanclass="n">data</span><spanclass="o">.</span><spanclass="n">reply</span><spanclass="p">(</span><spanclass="s2">"🚫 The message could not be deleted."</span><spanclass="p">)</span>
<spanclass="k">else</span><spanclass="p">:</span>
<spanclass="k">await</span><spanclass="n">data</span><spanclass="o">.</span><spanclass="n">reply</span><spanclass="p">(</span><spanclass="s2">"✅ The message was deleted!"</span><spanclass="p">)</span>
<h2>Sharing data between multiple calls<aclass="headerlink"href="#sharing-data-between-multiple-calls"title="Permalink to this headline">¶</a></h2>
<p>The <aclass="reference internal"href="../apireference.html#royalnet.commands.Command"title="royalnet.commands.Command"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">Command</span></code></a> 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:</p>
<spanclass="k">await</span><spanclass="n">data</span><spanclass="o">.</span><spanclass="n">reply</span><spanclass="p">(</span><spanclass="sa">f</span><spanclass="s2">"🍝 Here's your </span><spanclass="si">{</span><spanclass="n">args</span><spanclass="p">[</span><spanclass="mi">0</span><spanclass="p">]</span><spanclass="si">}</span><spanclass="s2">!</span><spanclass="se">\n</span><spanclass="s2">"</span>
<spanclass="sa">f</span><spanclass="s2">"[i]Spaghetti have been served </span><spanclass="si">{</span><spanclass="bp">self</span><spanclass="o">.</span><spanclass="n">__total_spaghetti</span><spanclass="si">}</span><spanclass="s2"> times.[/i]"</span><spanclass="p">)</span>
</pre></div>
</div>
<p>Values stored in this way persist <strong>only until the bot is restarted</strong>, and <strong>won’t be shared between different serfs</strong>; if you need persistent values, it is recommended to use a database through the Alchemy service.</p>
</div>
<divclass="section"id="using-the-alchemy">
<h2>Using the Alchemy<aclass="headerlink"href="#using-the-alchemy"title="Permalink to this headline">¶</a></h2>
<p>Royalnet can be connected to a PostgreSQL database through a special SQLAlchemy interface called
<p>If the connection is established, the <codeclass="docutils literal notranslate"><spanclass="pre">self.alchemy</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">data.session</span></code> fields will be
available for use in commands.</p>
<p><codeclass="docutils literal notranslate"><spanclass="pre">self.alchemy</span></code> is an instance of <aclass="reference internal"href="../apireference.html#royalnet.alchemy.Alchemy"title="royalnet.alchemy.Alchemy"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">royalnet.alchemy.Alchemy</span></code></a>, which contains the
<aclass="reference external"href="https://docs.sqlalchemy.org/en/13/core/connections.html#sqlalchemy.engine.Engine"title="(in SQLAlchemy v1.3)"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">sqlalchemy.engine.Engine</span></code></a>, metadata and tables, while <codeclass="docutils literal notranslate"><spanclass="pre">data.session</span></code> is a
<aclass="reference external"href="https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session"title="(in SQLAlchemy v1.3)"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">sqlalchemy.orm.session.Session</span></code></a>, and can be interacted in the same way as one.</p>
<divclass="section"id="querying-the-database">
<h3>Querying the database<aclass="headerlink"href="#querying-the-database"title="Permalink to this headline">¶</a></h3>
<p>You can <aclass="reference external"href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query"title="(in SQLAlchemy v1.3)"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">sqlalchemy.orm.query.Query</span></code></a> the database using the SQLAlchemy ORM.</p>
<p>The SQLAlchemy tables can be found inside <aclass="reference internal"href="../apireference.html#royalnet.alchemy.Alchemy"title="royalnet.alchemy.Alchemy"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">royalnet.alchemy.Alchemy</span></code></a> with the <aclass="reference internal"href="../apireference.html#royalnet.alchemy.Alchemy.get"title="royalnet.alchemy.Alchemy.get"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">royalnet.alchemy.Alchemy.get()</span></code></a> method:</p>
<h3>Adding filters to the query<aclass="headerlink"href="#adding-filters-to-the-query"title="Permalink to this headline">¶</a></h3>
<p>You can filter the query results with the <aclass="reference external"href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.filter"title="(in SQLAlchemy v1.3)"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">sqlalchemy.orm.query.Query.filter()</span></code></a> method.</p>
<divclass="admonition note">
<pclass="admonition-title">Note</p>
<p>Remember to always use a table column as first comparision element, as it won’t work otherwise.</p>
<h3>Ordering the results of a query<aclass="headerlink"href="#ordering-the-results-of-a-query"title="Permalink to this headline">¶</a></h3>
<p>You can order the query results in <strong>ascending order</strong> with the <aclass="reference external"href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.order_by"title="(in SQLAlchemy v1.3)"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">sqlalchemy.orm.query.Query.order_by()</span></code></a> method.</p>
<h3>Fetching the results of a query<aclass="headerlink"href="#fetching-the-results-of-a-query"title="Permalink to this headline">¶</a></h3>
<p>You can fetch the query results with the <aclass="reference external"href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.all"title="(in SQLAlchemy v1.3)"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">sqlalchemy.orm.query.Query.all()</span></code></a>,
<p>Remember to use <aclass="reference internal"href="../apireference.html#royalnet.utils.asyncify"title="royalnet.utils.asyncify"><codeclass="xref py py-func docutils literal notranslate"><spanclass="pre">royalnet.utils.asyncify()</span></code></a> when fetching results, as it may take a while!</p>
<p>Use <aclass="reference external"href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.all"title="(in SQLAlchemy v1.3)"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">sqlalchemy.orm.query.Query.all()</span></code></a> if you want a <aclass="reference external"href="https://docs.python.org/3.8/library/stdtypes.html#list"title="(in Python v3.8)"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">list</span></code></a> of <strong>all results</strong>:</p>
<p>Use <aclass="reference external"href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.first"title="(in SQLAlchemy v1.3)"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">sqlalchemy.orm.query.Query.first()</span></code></a> if you want <strong>the first result</strong> of the list, or <codeclass="docutils literal notranslate"><spanclass="pre">None</span></code> if
<p>Use <aclass="reference external"href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.one"title="(in SQLAlchemy v1.3)"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">sqlalchemy.orm.query.Query.one()</span></code></a> if you expect to have <strong>a single result</strong>, and you want the command to
raise an error if any different number of results is returned:</p>
<divclass="highlight-default notranslate"><divclass="highlight"><pre><span></span><spanclass="n">result</span><spanclass="p">:</span><spanclass="o">...</span><spanclass="o">=</span><spanclass="k">await</span><spanclass="n">asyncify</span><spanclass="p">(</span><spanclass="n">query</span><spanclass="o">.</span><spanclass="n">one</span><spanclass="p">)</span><spanclass="c1"># Raises an error if there are no results or more than a result.</span>
</pre></div>
</div>
<p>Use <aclass="reference external"href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.one_or_none"title="(in SQLAlchemy v1.3)"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">sqlalchemy.orm.query.Query.one_or_none()</span></code></a> if you expect to have <strong>a single result</strong>, or <strong>nothing</strong>, and
if you want the command to raise an error if the number of results is greater than one.</p>
<divclass="highlight-default notranslate"><divclass="highlight"><pre><span></span><spanclass="n">result</span><spanclass="p">:</span><spanclass="n">typing</span><spanclass="o">.</span><spanclass="n">Union</span><spanclass="p">[</span><spanclass="o">...</span><spanclass="p">,</span><spanclass="kc">None</span><spanclass="p">]</span><spanclass="o">=</span><spanclass="k">await</span><spanclass="n">asyncify</span><spanclass="p">(</span><spanclass="n">query</span><spanclass="o">.</span><spanclass="n">one_or_none</span><spanclass="p">)</span><spanclass="c1"># Raises an error if there is more than a result.</span>
</pre></div>
</div>
</div>
<divclass="section"id="more-alchemy">
<h3>More Alchemy<aclass="headerlink"href="#more-alchemy"title="Permalink to this headline">¶</a></h3>
<p>You can <strong>call an event</strong> from inside a command, and receive its return value.</p>
<p>This may be used for example to get data from a different platform, such as getting the users online in a specific Discord server.</p>
<p>You can call an event with the <aclass="reference internal"href="../apireference.html#royalnet.serf.Serf.call_herald_event"title="royalnet.serf.Serf.call_herald_event"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">Serf.call_herald_event()</span></code></a> method:</p>
<p>Errors raised by the event will also be raised by the <aclass="reference internal"href="../apireference.html#royalnet.serf.Serf.call_herald_event"title="royalnet.serf.Serf.call_herald_event"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">Serf.call_herald_event()</span></code></a> method as one of the exceptions described in the <spanclass="xref std std-ref">Raising errors</span> section.</p>
<h2>Distinguish between platforms<aclass="headerlink"href="#distinguish-between-platforms"title="Permalink to this headline">¶</a></h2>
<p>To see if a command is being run on a specific platform, you can check the type of the <codeclass="docutils literal notranslate"><spanclass="pre">self.serf</span></code> object:</p>
<spanclass="k">await</span><spanclass="n">data</span><spanclass="o">.</span><spanclass="n">reply</span><spanclass="p">(</span><spanclass="s2">"This command is being run on Telegram."</span><spanclass="p">)</span>
<spanclass="k">await</span><spanclass="n">data</span><spanclass="o">.</span><spanclass="n">reply</span><spanclass="p">(</span><spanclass="s2">"This command is being run on Discord."</span><spanclass="p">)</span>
<p>A keyboard is a message with multiple buttons (“keys”) attached which can be pressed by an user viewing the message.</p>
<p>Once a button is pressed, a callback function is run, which has its own <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandData"title="royalnet.commands.CommandData"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">CommandData</span></code></a> context and can do everything a regular comment call could.</p>
<p>The callback function is a coroutine accepting a single <codeclass="docutils literal notranslate"><spanclass="pre">data:</span><spanclass="pre">CommandData</span></code> argument:</p>
<spanclass="k">await</span><spanclass="n">data</span><spanclass="o">.</span><spanclass="n">reply</span><spanclass="p">(</span><spanclass="s2">"Spaghetti were ejected from your floppy drive!"</span><spanclass="p">)</span>
</pre></div>
</div>
<p>To create a new key, you can use the <aclass="reference internal"href="../apireference.html#royalnet.commands.KeyboardKey"title="royalnet.commands.KeyboardKey"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">KeyboardKey</span></code></a> class:</p>
<spanclass="n">short</span><spanclass="o">=</span><spanclass="s2">"⏏️"</span><spanclass="p">,</span><spanclass="c1"># An emoji representing the key on platforms the full message cannot be displayed</span>
<spanclass="n">text</span><spanclass="o">=</span><spanclass="s2">"Eject spaghetti from the floppy drive"</span><spanclass="p">,</span><spanclass="c1"># The text displayed on the key</span>
<spanclass="n">callback</span><spanclass="o">=</span><spanclass="n">answer</span><spanclass="c1"># The coroutine to call when the key is pressed.</span>
<spanclass="p">)</span>
</pre></div>
</div>
<p>To display a keyboard and wait for a keyboard press, you can use the <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandData.keyboard"title="royalnet.commands.CommandData.keyboard"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">keyboard()</span></code></a> 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.</p>
<divclass="highlight-default notranslate"><divclass="highlight"><pre><span></span><spanclass="k">async</span><spanclass="k">with</span><spanclass="n">data</span><spanclass="o">.</span><spanclass="n">keyboard</span><spanclass="p">(</span><spanclass="n">text</span><spanclass="o">=</span><spanclass="s2">"What kind of spaghetti would you want to order?"</span><spanclass="p">,</span><spanclass="n">keys</span><spanclass="o">=</span><spanclass="n">keyboard</span><spanclass="p">):</span>
<spanclass="c1"># This will keep the keyboard valid for 10 seconds</span>
<h3>Replies in callbacks<aclass="headerlink"href="#replies-in-callbacks"title="Permalink to this headline">¶</a></h3>
<p>Calls to <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandData.reply"title="royalnet.commands.CommandData.reply"><codeclass="xref py py-meth docutils literal notranslate"><spanclass="pre">reply()</span></code></a> made with the <aclass="reference internal"href="../apireference.html#royalnet.commands.CommandData"title="royalnet.commands.CommandData"><codeclass="xref py py-class docutils literal notranslate"><spanclass="pre">CommandData</span></code></a> 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.</p>
<h2>Reading data from the configuration file<aclass="headerlink"href="#reading-data-from-the-configuration-file"title="Permalink to this headline">¶</a></h2>
<p>You can read data from your pack’s configuration section through the <codeclass="xref py py-attr docutils literal notranslate"><spanclass="pre">config</span></code> attribute:</p>
<divclass="highlight-default notranslate"><divclass="highlight"><pre><span></span><spanclass="k">await</span><spanclass="n">data</span><spanclass="o">.</span><spanclass="n">reply</span><spanclass="p">(</span><spanclass="sa">f</span><spanclass="s2">"Here's your spaghetti </span><spanclass="si">{</span><spanclass="bp">self</span><spanclass="o">.</span><spanclass="n">config</span><spanclass="p">[</span><spanclass="s1">'spaghetti'</span><spanclass="p">][</span><spanclass="s1">'mode'</span><spanclass="p">]</span><spanclass="si">}</span><spanclass="s2">!"</span><spanclass="p">)</span>
<h2>Running code on Serf start<aclass="headerlink"href="#running-code-on-serf-start"title="Permalink to this headline">¶</a></h2>
<p>The code inside <codeclass="docutils literal notranslate"><spanclass="pre">__init__</span></code> is run only once, during the initialization step of the bot:</p>
<p>To run a job independently from the rest of the command, you can schedule the execution of a coroutine inside <codeclass="docutils literal notranslate"><spanclass="pre">__init__</span></code>:</p>
<ahref="star.html"class="btn btn-neutral float-right"title="Adding a Star to the Pack"accesskey="n"rel="next">Next <spanclass="fa fa-arrow-circle-right"></span></a>
<ahref="newpack.html"class="btn btn-neutral float-left"title="Creating a new Pack"accesskey="p"rel="prev"><spanclass="fa fa-arrow-circle-left"></span> Previous</a>
Built with <ahref="http://sphinx-doc.org/">Sphinx</a> using a <ahref="https://github.com/rtfd/sphinx_rtd_theme">theme</a> provided by <ahref="https://readthedocs.org">Read the Docs</a>.