mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
575 lines
No EOL
48 KiB
HTML
575 lines
No EOL
48 KiB
HTML
|
||
|
||
<!DOCTYPE html>
|
||
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
|
||
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
|
||
<head>
|
||
<meta charset="utf-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
||
<title>Royalnet Commands — Royalnet documentation</title>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<script type="text/javascript" src="_static/js/modernizr.min.js"></script>
|
||
|
||
|
||
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
|
||
<script type="text/javascript" src="_static/jquery.js"></script>
|
||
<script type="text/javascript" src="_static/underscore.js"></script>
|
||
<script type="text/javascript" src="_static/doctools.js"></script>
|
||
<script type="text/javascript" src="_static/language_data.js"></script>
|
||
|
||
<script type="text/javascript" src="_static/js/theme.js"></script>
|
||
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
|
||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||
<link rel="stylesheet" href="_static/rygdocs.css" type="text/css" />
|
||
<link rel="index" title="Index" href="genindex.html" />
|
||
<link rel="search" title="Search" href="search.html" />
|
||
<link rel="next" title="API Reference" href="apireference.html" />
|
||
<link rel="prev" title="Running Royalnet" href="runningroyalnet.html" />
|
||
</head>
|
||
|
||
<body class="wy-body-for-nav">
|
||
|
||
|
||
<div class="wy-grid-for-nav">
|
||
|
||
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
|
||
<div class="wy-side-scroll">
|
||
<div class="wy-side-nav-search" >
|
||
|
||
|
||
|
||
<a href="index.html" class="icon icon-home"> Royalnet
|
||
|
||
|
||
|
||
</a>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<div role="search">
|
||
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
|
||
<input type="text" name="q" placeholder="Search docs" />
|
||
<input type="hidden" name="check_keywords" value="yes" />
|
||
<input type="hidden" name="area" value="default" />
|
||
</form>
|
||
</div>
|
||
|
||
|
||
</div>
|
||
|
||
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<ul class="current">
|
||
<li class="toctree-l1"><a class="reference internal" href="runningroyalnet.html">Running Royalnet</a></li>
|
||
<li class="toctree-l1 current"><a class="current reference internal" href="#">Royalnet Commands</a><ul>
|
||
<li class="toctree-l2"><a class="reference internal" href="#creating-a-new-command">Creating a new Command</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#command-arguments">Command arguments</a><ul>
|
||
<li class="toctree-l3"><a class="reference internal" href="#direct-access">Direct access</a></li>
|
||
<li class="toctree-l3"><a class="reference internal" href="#optional-access">Optional access</a></li>
|
||
<li class="toctree-l3"><a class="reference internal" href="#full-string">Full string</a></li>
|
||
<li class="toctree-l3"><a class="reference internal" href="#regular-expressions">Regular expressions</a></li>
|
||
</ul>
|
||
</li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#raising-errors">Raising errors</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#running-code-at-the-initialization-of-the-bot">Running code at the initialization of the bot</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#coroutines-and-slow-operations">Coroutines and slow operations</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#delete-the-invoking-message">Delete the invoking message</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#using-the-database">Using the database</a><ul>
|
||
<li class="toctree-l3"><a class="reference internal" href="#querying-the-database">Querying the database</a></li>
|
||
<li class="toctree-l3"><a class="reference internal" href="#adding-filters-to-the-query">Adding filters to the query</a></li>
|
||
<li class="toctree-l3"><a class="reference internal" href="#ordering-the-results-of-a-query">Ordering the results of a query</a></li>
|
||
<li class="toctree-l3"><a class="reference internal" href="#fetching-the-results-of-a-query">Fetching the results of a query</a></li>
|
||
<li class="toctree-l3"><a class="reference internal" href="#more-alchemy">More Alchemy</a></li>
|
||
</ul>
|
||
</li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#comunicating-via-royalnet">Comunicating via Royalnet</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#adding-the-command-to-a-pack">Adding the command to a Pack</a></li>
|
||
</ul>
|
||
</li>
|
||
<li class="toctree-l1"><a class="reference internal" href="apireference.html">API Reference</a></li>
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||
|
||
|
||
<nav class="wy-nav-top" aria-label="top navigation">
|
||
|
||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||
<a href="index.html">Royalnet</a>
|
||
|
||
</nav>
|
||
|
||
|
||
<div class="wy-nav-content">
|
||
|
||
<div class="rst-content">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<div role="navigation" aria-label="breadcrumbs navigation">
|
||
|
||
<ul class="wy-breadcrumbs">
|
||
|
||
<li><a href="index.html">Docs</a> »</li>
|
||
|
||
<li>Royalnet Commands</li>
|
||
|
||
|
||
<li class="wy-breadcrumbs-aside">
|
||
|
||
|
||
<a href="_sources/creatingacommand.rst.txt" rel="nofollow"> View page source</a>
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
<hr/>
|
||
</div>
|
||
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||
<div itemprop="articleBody">
|
||
|
||
<div class="section" id="royalnet-commands">
|
||
<h1>Royalnet Commands<a class="headerlink" href="#royalnet-commands" title="Permalink to this headline">¶</a></h1>
|
||
<p>A Royalnet Command is a small script that is run whenever a specific message is sent to a Royalnet interface.</p>
|
||
<p>A Command code looks like this:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">royalnet.commands</span> <span class="k">import</span> <span class="n">Command</span>
|
||
|
||
<span class="k">class</span> <span class="nc">PingCommand</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="s2">"ping"</span>
|
||
|
||
<span class="n">description</span> <span class="o">=</span> <span class="s2">"Play ping-pong with the bot."</span>
|
||
|
||
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">interface</span><span class="p">):</span>
|
||
<span class="c1"># This code is run just once, while the bot is starting</span>
|
||
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">()</span>
|
||
|
||
<span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
|
||
<span class="c1"># This code is run every time the command is called</span>
|
||
<span class="k">await</span> <span class="n">data</span><span class="o">.</span><span class="n">reply</span><span class="p">(</span><span class="s2">"Pong!"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<div class="section" id="creating-a-new-command">
|
||
<h2>Creating a new Command<a class="headerlink" href="#creating-a-new-command" title="Permalink to this headline">¶</a></h2>
|
||
<p>First, think of a <code class="docutils literal notranslate"><span class="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 <code class="docutils literal notranslate"><span class="pre">name</span></code> you have thought of.
|
||
The previously mentioned “spaghetti” command should have a file called <code class="docutils literal notranslate"><span class="pre">spaghetti.py</span></code>.</p>
|
||
<p>Then, in the first row of the file, import the <code class="xref py py-class docutils literal notranslate"><span class="pre">Command</span></code> class from royalnet, and create a new class inheriting from it:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">royalnet.commands</span> <span class="k">import</span> <span class="n">Command</span>
|
||
|
||
<span class="k">class</span> <span class="nc">SpaghettiCommand</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
|
||
<span class="o">...</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Inside the class, override the attributes <code class="docutils literal notranslate"><span class="pre">name</span></code> and <code class="docutils literal notranslate"><span class="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>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">royalnet.commands</span> <span class="k">import</span> <span class="n">Command</span>
|
||
|
||
<span class="k">class</span> <span class="nc">SpaghettiCommand</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="s2">"spaghetti"</span>
|
||
|
||
<span class="n">description</span> <span class="o">=</span> <span class="s2">"Send a spaghetti emoji in the chat."</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Now override the <code class="xref py py-meth docutils literal notranslate"><span class="pre">Command.run()</span></code> 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 <code class="xref py py-meth docutils literal notranslate"><span class="pre">CommandData.reply()</span></code> method:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">royalnet.commands</span> <span class="k">import</span> <span class="n">Command</span>
|
||
|
||
<span class="k">class</span> <span class="nc">SpaghettiCommand</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="s2">"spaghetti"</span>
|
||
|
||
<span class="n">description</span> <span class="o">=</span> <span class="s2">"Send a spaghetti emoji in the chat."</span>
|
||
|
||
<span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
|
||
<span class="k">await</span> <span class="n">data</span><span class="o">.</span><span class="n">reply</span><span class="p">(</span><span class="s2">"🍝"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>And… it’s done! The command is ready to be added to a Pack!</p>
|
||
</div>
|
||
<div class="section" id="command-arguments">
|
||
<h2>Command arguments<a class="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 <a class="reference external" href="https://docs.python.org/3.7/library/stdtypes.html#str" title="(in Python v3.7)"><code class="xref py py-class docutils literal notranslate"><span class="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 <code class="docutils literal notranslate"><span class="pre">args</span></code> parameter passed to the <code class="xref py py-meth docutils literal notranslate"><span class="pre">Command.run()</span></code>
|
||
method.</p>
|
||
<p>If you want your command to use arguments, override the <code class="docutils literal notranslate"><span class="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
|
||
ones.</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">royalnet.commands</span> <span class="k">import</span> <span class="n">Command</span>
|
||
|
||
<span class="k">class</span> <span class="nc">SpaghettiCommand</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="s2">"spaghetti"</span>
|
||
|
||
<span class="n">description</span> <span class="o">=</span> <span class="s2">"Send a spaghetti emoji in the chat."</span>
|
||
|
||
<span class="n">syntax</span> <span class="o">=</span> <span class="s2">"(requestedpasta)"</span>
|
||
|
||
<span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
|
||
<span class="k">await</span> <span class="n">data</span><span class="o">.</span><span class="n">reply</span><span class="p">(</span><span class="n">f</span><span class="s2">"🍝 Here's your </span><span class="si">{args[0]}</span><span class="s2">!"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<div class="section" id="direct-access">
|
||
<h3>Direct access<a class="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 <a class="reference external" href="https://docs.python.org/3.7/library/stdtypes.html#str" title="(in Python v3.7)"><code class="xref py py-class docutils literal notranslate"><span class="pre">str</span></code></a>.</p>
|
||
<p>If you request an argument with a certain number, but the argument does not exist, an
|
||
<code class="xref py py-exc docutils literal notranslate"><span class="pre">royalnet.error.InvalidInputError</span></code> is raised, making the arguments accessed in this way <strong>required</strong>.</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
|
||
<span class="c1"># "carbonara"</span>
|
||
|
||
<span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
|
||
<span class="c1"># "al-dente"</span>
|
||
|
||
<span class="n">args</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
|
||
<span class="c1"># InvalidInputError() is raised</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="optional-access">
|
||
<h3>Optional access<a class="headerlink" href="#optional-access" title="Permalink to this headline">¶</a></h3>
|
||
<p>If you don’t want arguments to be required, you can access them through the <code class="xref py py-meth docutils literal notranslate"><span class="pre">CommandArgs.optional()</span></code> method: it
|
||
will return <code class="xref py py-const docutils literal notranslate"><span class="pre">None</span></code> if the argument wasn’t passed, making it <strong>optional</strong>.</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">args</span><span class="o">.</span><span class="n">optional</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
|
||
<span class="c1"># "carbonara"</span>
|
||
|
||
<span class="n">args</span><span class="o">.</span><span class="n">optional</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
|
||
<span class="c1"># "al-dente"</span>
|
||
|
||
<span class="n">args</span><span class="o">.</span><span class="n">optional</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
|
||
<span class="c1"># None</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>You can specify a default result too, so that the method will return it instead of returning <code class="xref py py-const docutils literal notranslate"><span class="pre">None</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">args</span><span class="o">.</span><span class="n">optional</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">"banana"</span><span class="p">)</span>
|
||
<span class="c1"># "banana"</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="full-string">
|
||
<h3>Full string<a class="headerlink" href="#full-string" title="Permalink to this headline">¶</a></h3>
|
||
<p>If you want the full argument string, you can use the <code class="xref py py-meth docutils literal notranslate"><span class="pre">CommandArgs.joined()</span></code> method.</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">args</span><span class="o">.</span><span class="n">joined</span><span class="p">()</span>
|
||
<span class="c1"># "carbonara al-dente"</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>You can specify a minimum number of arguments too, so that an <code class="xref py py-exc docutils literal notranslate"><span class="pre">InvalidInputError</span></code> will be
|
||
raised if not enough arguments are present:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">args</span><span class="o">.</span><span class="n">joined</span><span class="p">(</span><span class="n">require_at_least</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span>
|
||
<span class="c1"># InvalidInputError() is raised</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="regular-expressions">
|
||
<h3>Regular expressions<a class="headerlink" href="#regular-expressions" title="Permalink to this headline">¶</a></h3>
|
||
<p>For more complex commands, you may want to get arguments through <a class="reference external" href="https://regexr.com/">regular expressions</a>.</p>
|
||
<p>You can then use the <code class="xref py py-meth docutils literal notranslate"><span class="pre">CommandArgs.match()</span></code> method, which tries to match a pattern to the command argument string,
|
||
which returns a tuple of the matched groups and raises an <code class="xref py py-exc docutils literal notranslate"><span class="pre">InvalidInputError</span></code> if there is no match.</p>
|
||
<p>To match a pattern, <a class="reference external" href="https://docs.python.org/3.7/library/re.html#re.match" title="(in Python v3.7)"><code class="xref py py-func docutils literal notranslate"><span class="pre">re.match()</span></code></a> is used, meaning that Python will try to match only at the beginning of the string.</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">args</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sa">r</span><span class="s2">"(carb\w+)"</span><span class="p">)</span>
|
||
<span class="c1"># ("carbonara",)</span>
|
||
|
||
<span class="n">args</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sa">r</span><span class="s2">"(al-\w+)"</span><span class="p">)</span>
|
||
<span class="c1"># InvalidInputError() is raised</span>
|
||
|
||
<span class="n">args</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sa">r</span><span class="s2">"\s*(al-\w+)"</span><span class="p">)</span>
|
||
<span class="c1"># ("al-dente",)</span>
|
||
|
||
<span class="n">args</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sa">r</span><span class="s2">"\s*(carb\w+)\s*(al-\w+)"</span><span class="p">)</span>
|
||
<span class="c1"># ("carbonara", "al-dente")</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="raising-errors">
|
||
<h2>Raising errors<a class="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 <code class="xref py py-exc docutils literal notranslate"><span class="pre">CommandError</span></code> using the error message as argument:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">if</span> <span class="ow">not</span> <span class="n">kitchen</span><span class="o">.</span><span class="n">is_open</span><span class="p">():</span>
|
||
<span class="k">raise</span> <span class="n">CommandError</span><span class="p">(</span><span class="s2">"The kitchen is closed. Come back later!"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>You can also manually raise <code class="xref py py-exc docutils literal notranslate"><span class="pre">InvalidInputError</span></code> to redisplay the command syntax, along with your error message:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">if</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">allowed_pasta</span><span class="p">:</span>
|
||
<span class="k">raise</span> <span class="n">InvalidInputError</span><span class="p">(</span><span class="s2">"The specified pasta type is invalid."</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>If you need a Royalnet feature that’s not available on the current interface, you can raise an
|
||
<code class="xref py py-exc docutils literal notranslate"><span class="pre">UnsupportedError</span></code> with a brief description of what’s missing:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">if</span> <span class="n">interface</span><span class="o">.</span><span class="n">name</span> <span class="o">!=</span> <span class="s2">"telegram"</span><span class="p">:</span>
|
||
<span class="k">raise</span> <span class="n">UnsupportedError</span><span class="p">(</span><span class="s2">"This command can only be run on Telegram interfaces."</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="running-code-at-the-initialization-of-the-bot">
|
||
<h2>Running code at the initialization of the bot<a class="headerlink" href="#running-code-at-the-initialization-of-the-bot" title="Permalink to this headline">¶</a></h2>
|
||
<p>You can run code while the bot is starting by overriding the <code class="xref py py-meth docutils literal notranslate"><span class="pre">Command.__init__()</span></code> function.</p>
|
||
<p>You should keep the <code class="docutils literal notranslate"><span class="pre">super().__init__(interface)</span></code> call at the start of it, so that the <code class="xref py py-class docutils literal notranslate"><span class="pre">Command</span></code> instance is
|
||
initialized properly, then add your code after it.</p>
|
||
<p>You can add fields to the command to keep <strong>shared data between multiple command calls</strong> (but not bot restarts): it may
|
||
be useful for fetching external static data and keeping it until the bot is restarted, or to store references to all the
|
||
<a class="reference external" href="https://docs.python.org/3.7/library/asyncio-task.html#asyncio.Task" title="(in Python v3.7)"><code class="xref py py-class docutils literal notranslate"><span class="pre">asyncio.Task</span></code></a> started by the bot.</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">royalnet.commands</span> <span class="k">import</span> <span class="n">Command</span>
|
||
|
||
<span class="k">class</span> <span class="nc">SpaghettiCommand</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="s2">"spaghetti"</span>
|
||
|
||
<span class="n">description</span> <span class="o">=</span> <span class="s2">"Send a spaghetti emoji in the chat."</span>
|
||
|
||
<span class="n">syntax</span> <span class="o">=</span> <span class="s2">"</span><span class="si">{pasta}</span><span class="s2">"</span>
|
||
|
||
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">interface</span><span class="p">):</span>
|
||
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">interface</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">requested_pasta</span> <span class="o">=</span> <span class="p">[]</span>
|
||
|
||
<span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
|
||
<span class="n">pasta</span> <span class="o">=</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
|
||
<span class="k">if</span> <span class="n">pasta</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">requested_pasta</span><span class="p">:</span>
|
||
<span class="k">await</span> <span class="n">data</span><span class="o">.</span><span class="n">reply</span><span class="p">(</span><span class="n">f</span><span class="s2">"⚠️ This pasta was already requested before."</span><span class="p">)</span>
|
||
<span class="k">return</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">requested_pasta</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">pasta</span><span class="p">)</span>
|
||
<span class="k">await</span> <span class="n">data</span><span class="o">.</span><span class="n">reply</span><span class="p">(</span><span class="n">f</span><span class="s2">"🍝 Here's your </span><span class="si">{pasta}</span><span class="s2">!"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="coroutines-and-slow-operations">
|
||
<h2>Coroutines and slow operations<a class="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 <code class="docutils literal notranslate"><span class="pre">await</span> <span class="pre">data.reply("🍝")</span></code> instead of just <code class="docutils literal notranslate"><span class="pre">data.reply("🍝")</span></code>.</p>
|
||
<p>This is because <code class="xref py py-meth docutils literal notranslate"><span class="pre">CommandData.reply()</span></code> 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 <code class="docutils literal notranslate"><span class="pre">await</span></code> keyword before the <code class="docutils literal notranslate"><span class="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>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
|
||
<span class="c1"># Don't do this!</span>
|
||
<span class="n">image</span> <span class="o">=</span> <span class="n">download_1_terabyte_of_spaghetti</span><span class="p">(</span><span class="s2">"right_now"</span><span class="p">,</span> <span class="n">from</span><span class="o">=</span><span class="s2">"italy"</span><span class="p">)</span>
|
||
<span class="o">...</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>If the slow function you want does not cause any side effect, you can wrap it with the <code class="xref py py-func docutils literal notranslate"><span class="pre">royalnet.utils.asyncify()</span></code>
|
||
function:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
|
||
<span class="c1"># If the called function has no side effect, you can do this!</span>
|
||
<span class="n">image</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncify</span><span class="p">(</span><span class="n">download_1_terabyte_of_spaghetti</span><span class="p">,</span> <span class="s2">"right_now"</span><span class="p">,</span> <span class="n">from</span><span class="o">=</span><span class="s2">"italy"</span><span class="p">)</span>
|
||
<span class="o">...</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Avoid using <a class="reference external" href="https://docs.python.org/3.7/library/time.html#time.sleep" title="(in Python v3.7)"><code class="xref py py-func docutils literal notranslate"><span class="pre">time.sleep()</span></code></a> function, as it is considered a slow operation: use instead <a class="reference external" href="https://docs.python.org/3.7/library/asyncio-task.html#asyncio.sleep" title="(in Python v3.7)"><code class="xref py py-func docutils literal notranslate"><span class="pre">asyncio.sleep()</span></code></a>,
|
||
a coroutine that does the same exact thing.</p>
|
||
</div>
|
||
<div class="section" id="delete-the-invoking-message">
|
||
<h2>Delete the invoking message<a class="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 <code class="docutils literal notranslate"><span class="pre">/spaghetti</span> <span class="pre">carbonara</span></code> is the invoking message for the <code class="docutils literal notranslate"><span class="pre">spaghetti</span></code> command run.</p>
|
||
<p>You can have the bot delete the invoking message for a command by calling the <code class="xref py py-class docutils literal notranslate"><span class="pre">CommandData.delete_invoking</span></code>
|
||
method:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
|
||
<span class="k">await</span> <span class="n">data</span><span class="o">.</span><span class="n">delete_invoking</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Not all interfaces support deleting messages; by default, if the interface does not support deletions, the call is
|
||
ignored.</p>
|
||
<p>You can have the method raise an error if the message can’t be deleted by setting the <code class="docutils literal notranslate"><span class="pre">error_if_unavailable</span></code> parameter
|
||
to True:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="k">await</span> <span class="n">data</span><span class="o">.</span><span class="n">delete_invoking</span><span class="p">(</span><span class="n">error_if_unavailable</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">except</span> <span class="n">royalnet</span><span class="o">.</span><span class="n">error</span><span class="o">.</span><span class="n">UnsupportedError</span><span class="p">:</span>
|
||
<span class="k">await</span> <span class="n">data</span><span class="o">.</span><span class="n">reply</span><span class="p">(</span><span class="s2">"🚫 The message could not be deleted."</span><span class="p">)</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="k">await</span> <span class="n">data</span><span class="o">.</span><span class="n">reply</span><span class="p">(</span><span class="s2">"✅ The message was deleted!"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="using-the-database">
|
||
<h2>Using the database<a class="headerlink" href="#using-the-database" title="Permalink to this headline">¶</a></h2>
|
||
<p>Bots can be connected to a PostgreSQL database through a special SQLAlchemy interface called
|
||
<code class="xref py py-class docutils literal notranslate"><span class="pre">royalnet.database.Alchemy</span></code>.</p>
|
||
<p>If the connection is established, the <code class="docutils literal notranslate"><span class="pre">self.alchemy</span></code> and <code class="docutils literal notranslate"><span class="pre">data.session</span></code> fields will be
|
||
available for use in commands.</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">self.interface.alchemy</span></code> is an instance of <code class="xref py py-class docutils literal notranslate"><span class="pre">royalnet.database.Alchemy</span></code>, which contains the
|
||
<a class="reference external" href="https://docs.sqlalchemy.org/en/13/core/connections.html#sqlalchemy.engine.Engine" title="(in SQLAlchemy v1.3)"><code class="xref py py-class docutils literal notranslate"><span class="pre">sqlalchemy.engine.Engine</span></code></a>, metadata and tables, while <code class="docutils literal notranslate"><span class="pre">data.session</span></code> is a
|
||
<a class="reference external" href="https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session" title="(in SQLAlchemy v1.3)"><code class="xref py py-class docutils literal notranslate"><span class="pre">sqlalchemy.orm.session.Session</span></code></a>, and can be interacted in the same way as one.</p>
|
||
<p>If you want to use <code class="xref py py-class docutils literal notranslate"><span class="pre">royalnet.database.Alchemy</span></code> in your command, you should override the
|
||
<code class="docutils literal notranslate"><span class="pre">tables</span></code> field with the <a class="reference external" href="https://docs.python.org/3.7/library/stdtypes.html#set" title="(in Python v3.7)"><code class="xref py py-class docutils literal notranslate"><span class="pre">set</span></code></a> of Alchemy tables you need.</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">royalnet.commands</span> <span class="k">import</span> <span class="n">Command</span>
|
||
<span class="kn">from</span> <span class="nn">royalnet.packs.common.tables</span> <span class="k">import</span> <span class="n">User</span>
|
||
|
||
<span class="k">class</span> <span class="nc">SpaghettiCommand</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="s2">"spaghetti"</span>
|
||
|
||
<span class="n">description</span> <span class="o">=</span> <span class="s2">"Send a spaghetti emoji in the chat."</span>
|
||
|
||
<span class="n">syntax</span> <span class="o">=</span> <span class="s2">"</span><span class="si">{pasta}</span><span class="s2">"</span>
|
||
|
||
<span class="n">tables</span> <span class="o">=</span> <span class="p">{</span><span class="n">User</span><span class="p">}</span>
|
||
|
||
<span class="o">...</span>
|
||
</pre></div>
|
||
</div>
|
||
<div class="section" id="querying-the-database">
|
||
<h3>Querying the database<a class="headerlink" href="#querying-the-database" title="Permalink to this headline">¶</a></h3>
|
||
<p>You can <a class="reference external" href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query" title="(in SQLAlchemy v1.3)"><code class="xref py py-class docutils literal notranslate"><span class="pre">sqlalchemy.orm.query.Query</span></code></a> the database using the SQLAlchemy ORM.</p>
|
||
<p>The SQLAlchemy tables can be found inside <code class="xref py py-class docutils literal notranslate"><span class="pre">royalnet.database.Alchemy</span></code> with the same name they were created
|
||
from, if they were specified in <code class="docutils literal notranslate"><span class="pre">tables</span></code>.</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">query</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">query</span><span class="p">(</span><span class="n">User</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="adding-filters-to-the-query">
|
||
<h3>Adding filters to the query<a class="headerlink" href="#adding-filters-to-the-query" title="Permalink to this headline">¶</a></h3>
|
||
<p>You can filter the query results with the <a class="reference external" href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.filter" title="(in SQLAlchemy v1.3)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">sqlalchemy.orm.query.Query.filter()</span></code></a> method.</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Remember to always use a table column as first comparision element, as it won’t work otherwise.</p>
|
||
</div>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">query</span> <span class="o">=</span> <span class="n">query</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">User</span><span class="o">.</span><span class="n">role</span> <span class="o">==</span> <span class="s2">"Member"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="ordering-the-results-of-a-query">
|
||
<h3>Ordering the results of a query<a class="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 <a class="reference external" href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.order_by" title="(in SQLAlchemy v1.3)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">sqlalchemy.orm.query.Query.order_by()</span></code></a> method.</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">query</span> <span class="o">=</span> <span class="n">query</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="n">User</span><span class="o">.</span><span class="n">username</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Additionally, you can append the <cite>.desc()</cite> method to a table column to sort in <strong>descending order</strong>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">query</span> <span class="o">=</span> <span class="n">query</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="n">User</span><span class="o">.</span><span class="n">username</span><span class="o">.</span><span class="n">desc</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="fetching-the-results-of-a-query">
|
||
<h3>Fetching the results of a query<a class="headerlink" href="#fetching-the-results-of-a-query" title="Permalink to this headline">¶</a></h3>
|
||
<p>You can fetch the query results with the <a class="reference external" href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.all" title="(in SQLAlchemy v1.3)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">sqlalchemy.orm.query.Query.all()</span></code></a>,
|
||
<a class="reference external" href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.first" title="(in SQLAlchemy v1.3)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">sqlalchemy.orm.query.Query.first()</span></code></a>, <a class="reference external" href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.one" title="(in SQLAlchemy v1.3)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">sqlalchemy.orm.query.Query.one()</span></code></a> and
|
||
<a class="reference external" href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.one_or_none" title="(in SQLAlchemy v1.3)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">sqlalchemy.orm.query.Query.one_or_none()</span></code></a> methods.</p>
|
||
<p>Remember to use <code class="xref py py-func docutils literal notranslate"><span class="pre">royalnet.utils.asyncify()</span></code> when fetching results, as it may take a while!</p>
|
||
<p>Use <a class="reference external" href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.all" title="(in SQLAlchemy v1.3)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">sqlalchemy.orm.query.Query.all()</span></code></a> if you want a <a class="reference external" href="https://docs.python.org/3.7/library/stdtypes.html#list" title="(in Python v3.7)"><code class="xref py py-class docutils literal notranslate"><span class="pre">list</span></code></a> of <strong>all results</strong>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">results</span><span class="p">:</span> <span class="nb">list</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncify</span><span class="p">(</span><span class="n">query</span><span class="o">.</span><span class="n">all</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Use <a class="reference external" href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.first" title="(in SQLAlchemy v1.3)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">sqlalchemy.orm.query.Query.first()</span></code></a> if you want <strong>the first result</strong> of the list, or <code class="xref py py-const docutils literal notranslate"><span class="pre">None</span></code> if
|
||
there are no results:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">result</span><span class="p">:</span> <span class="n">typing</span><span class="o">.</span><span class="n">Union</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncify</span><span class="p">(</span><span class="n">query</span><span class="o">.</span><span class="n">first</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Use <a class="reference external" href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.one" title="(in SQLAlchemy v1.3)"><code class="xref py py-meth docutils literal notranslate"><span class="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>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">result</span><span class="p">:</span> <span class="o">...</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncify</span><span class="p">(</span><span class="n">query</span><span class="o">.</span><span class="n">one</span><span class="p">)</span> <span class="c1"># Raises an error if there are no results or more than a result.</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Use <a class="reference external" href="https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.one_or_none" title="(in SQLAlchemy v1.3)"><code class="xref py py-meth docutils literal notranslate"><span class="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>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">result</span><span class="p">:</span> <span class="n">typing</span><span class="o">.</span><span class="n">Union</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncify</span><span class="p">(</span><span class="n">query</span><span class="o">.</span><span class="n">one_or_none</span><span class="p">)</span> <span class="c1"># Raises an error if there is more than a result.</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="more-alchemy">
|
||
<h3>More Alchemy<a class="headerlink" href="#more-alchemy" title="Permalink to this headline">¶</a></h3>
|
||
<p>You can read more about <code class="xref py py-mod docutils literal notranslate"><span class="pre">sqlalchemy</span></code> at their <a class="reference external" href="https://www.sqlalchemy.org/">website</a>.</p>
|
||
</div>
|
||
</div>
|
||
<div class="section" id="comunicating-via-royalnet">
|
||
<h2>Comunicating via Royalnet<a class="headerlink" href="#comunicating-via-royalnet" title="Permalink to this headline">¶</a></h2>
|
||
<p>This section is not documented yet.</p>
|
||
</div>
|
||
<div class="section" id="adding-the-command-to-a-pack">
|
||
<h2>Adding the command to a Pack<a class="headerlink" href="#adding-the-command-to-a-pack" title="Permalink to this headline">¶</a></h2>
|
||
<p>This section is not documented yet.</p>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
<footer>
|
||
|
||
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
|
||
|
||
<a href="apireference.html" class="btn btn-neutral float-right" title="API Reference" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right"></span></a>
|
||
|
||
|
||
<a href="runningroyalnet.html" class="btn btn-neutral float-left" title="Running Royalnet" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left"></span> Previous</a>
|
||
|
||
</div>
|
||
|
||
|
||
<hr/>
|
||
|
||
<div role="contentinfo">
|
||
<p>
|
||
© Copyright 2019, Stefano Pigozzi
|
||
|
||
</p>
|
||
</div>
|
||
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/rtfd/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||
|
||
</footer>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</section>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script type="text/javascript">
|
||
jQuery(function () {
|
||
SphinxRtdTheme.Navigation.enable(true);
|
||
});
|
||
</script>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</body>
|
||
</html> |