mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 11:34:18 +00:00
A lot of new stuff is coming!
This commit is contained in:
parent
22c3cd6351
commit
9cbcce32da
37 changed files with 608 additions and 273 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -137,7 +137,7 @@ If you want the full argument string, you can use the :py:meth:`CommandArgs.join
|
|||
args.joined()
|
||||
# "carbonara al-dente"
|
||||
|
||||
You can specify a minimum number of arguments too, so that an :py:exc:`royalnet.error.InvalidInputError` will be
|
||||
You can specify a minimum number of arguments too, so that an :py:exc:`InvalidInputError` will be
|
||||
raised if not enough arguments are present: ::
|
||||
|
||||
args.joined(require_at_least=3)
|
||||
|
@ -149,7 +149,7 @@ Regular expressions
|
|||
For more complex commands, you may want to get arguments through `regular expressions <https://regexr.com/>`_.
|
||||
|
||||
You can then use the :py: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 :py:exc:`royalnet.error.InvalidInputError` if there is no match.
|
||||
which returns a tuple of the matched groups and raises an :py:exc:`InvalidInputError` if there is no match.
|
||||
|
||||
To match a pattern, :py:func:`re.match` is used, meaning that Python will try to match only at the beginning of the string. ::
|
||||
|
||||
|
@ -165,6 +165,25 @@ To match a pattern, :py:func:`re.match` is used, meaning that Python will try to
|
|||
args.match(r"\s*(carb\w+)\s*(al-\w+)")
|
||||
# ("carbonara", "al-dente")
|
||||
|
||||
Raising errors
|
||||
---------------------------------------------
|
||||
|
||||
If you want to display an error message to the user, you can raise a :py:exc:`CommandError` using the error message as argument: ::
|
||||
|
||||
if not kitchen.is_open():
|
||||
raise CommandError("The kitchen is closed. Come back later!")
|
||||
|
||||
You can also manually raise :py:exc:`InvalidInputError` to redisplay the command syntax, along with your error message: ::
|
||||
|
||||
if args[0] not in allowed_pasta:
|
||||
raise InvalidInputError("The specified pasta type is invalid.")
|
||||
|
||||
If you need a Royalnet feature that's not available on the current interface, you can raise an
|
||||
:py:exc:`UnsupportedError` with a brief description of what's missing: ::
|
||||
|
||||
if interface.name != "telegram":
|
||||
raise UnsupportedError("This command can only be run on Telegram interfaces.")
|
||||
|
||||
Running code at the initialization of the bot
|
||||
---------------------------------------------
|
||||
|
||||
|
|
|
@ -3,4 +3,60 @@
|
|||
Running Royalnet
|
||||
====================================
|
||||
|
||||
This documentation page hasn't been written yet, please refer to the README until then.
|
||||
To run a ``royalnet`` instance, you have first to download the package from ``pip``:
|
||||
|
||||
The Keyring
|
||||
------------------------------------
|
||||
::
|
||||
|
||||
pip install royalnet
|
||||
|
||||
|
||||
To run ``royalnet``, you'll have to setup the system keyring.
|
||||
|
||||
On Windows and desktop Linux, this is already configured;
|
||||
on a headless Linux instance, you'll need to `manually start and unlock the keyring daemon
|
||||
<https://keyring.readthedocs.io/en/latest/#using-keyring-on-headless-linux-systems>`_.
|
||||
|
||||
Now you have to create a new ``royalnet`` configuration. Start the configuration wizard: ::
|
||||
|
||||
python -m royalnet.configurator
|
||||
|
||||
You'll be prompted to enter a "secrets name": this is the name of the group of API keys that will be associated with
|
||||
your bot. Enter a name that you'll be able to remember. ::
|
||||
|
||||
Desired secrets name [__default__]: royalgames
|
||||
|
||||
You'll then be asked for a network password.
|
||||
|
||||
This password is used to connect to the rest of the :py:mod:`royalnet.network`, or, if you're hosting a local Network,
|
||||
it will be the necessary password to connect to it: ::
|
||||
|
||||
Network password []: cosafaunapesuunafoglia
|
||||
|
||||
Then you'll be asked for a Telegram Bot API token.
|
||||
You can get one from `@BotFather <https://t.me/BotFather>`_. ::
|
||||
|
||||
Telegram Bot API token []: 000000000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
||||
The next prompt will ask for a Discord Bot API token.
|
||||
You can get one at the `Discord Developers Portal <https://discordapp.com/developers/applications/>`_. ::
|
||||
|
||||
Discord Bot API token []: AAAAAAAAAAAAAAAAAAAAAAAA.AAAAAA.AAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
||||
Now the configurator will ask you for a Imgur API token.
|
||||
`Register an application <https://api.imgur.com/oauth2/addclient>`_ on Imgur to be supplied one.
|
||||
The token should be of type "anonymous usage without user authorization". ::
|
||||
|
||||
Imgur API token []: aaaaaaaaaaaaaaa
|
||||
|
||||
Next, you'll be asked for a Sentry DSN. You probably won't have one, so just ignore it and press enter. ::
|
||||
|
||||
Sentry DSN []:
|
||||
|
||||
Now that all tokens are configured, you're ready to launch the bot!
|
||||
|
||||
Running the bots
|
||||
------------------------------------
|
||||
|
||||
TODO
|
|
@ -286,11 +286,6 @@
|
|||
<code class="sig-name descname">convert_to_pcm</code><span class="sig-paren">(</span><span class="sig-paren">)</span> → None<a class="headerlink" href="#royalnet.audio.YtdlDiscord.convert_to_pcm" title="Permalink to this definition">¶</a></dt>
|
||||
<dd></dd></dl>
|
||||
|
||||
<dl class="method">
|
||||
<dt id="royalnet.audio.YtdlDiscord.create_and_ready_from_url">
|
||||
<em class="property">classmethod </em><code class="sig-name descname">create_and_ready_from_url</code><span class="sig-paren">(</span><em class="sig-param">url</em>, <em class="sig-param">**ytdl_args</em><span class="sig-paren">)</span> → List[royalnet.audio.ytdldiscord.YtdlDiscord]<a class="headerlink" href="#royalnet.audio.YtdlDiscord.create_and_ready_from_url" title="Permalink to this definition">¶</a></dt>
|
||||
<dd></dd></dl>
|
||||
|
||||
<dl class="method">
|
||||
<dt id="royalnet.audio.YtdlDiscord.create_from_url">
|
||||
<em class="property">classmethod </em><code class="sig-name descname">create_from_url</code><span class="sig-paren">(</span><em class="sig-param">url</em>, <em class="sig-param">**ytdl_args</em><span class="sig-paren">)</span> → List[royalnet.audio.ytdldiscord.YtdlDiscord]<a class="headerlink" href="#royalnet.audio.YtdlDiscord.create_from_url" title="Permalink to this definition">¶</a></dt>
|
||||
|
@ -573,7 +568,7 @@ find the chain that links the <code class="docutils literal notranslate"><span c
|
|||
|
||||
<dl class="method">
|
||||
<dt id="royalnet.bots.GenericBot.run_blocking">
|
||||
<code class="sig-name descname">run_blocking</code><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.bots.GenericBot.run_blocking" title="Permalink to this definition">¶</a></dt>
|
||||
<code class="sig-name descname">run_blocking</code><span class="sig-paren">(</span><em class="sig-param">verbose=False</em><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.bots.GenericBot.run_blocking" title="Permalink to this definition">¶</a></dt>
|
||||
<dd></dd></dl>
|
||||
|
||||
<dl class="method">
|
||||
|
@ -657,6 +652,13 @@ find the chain that links the <code class="docutils literal notranslate"><span c
|
|||
<dt id="royalnet.commands.Command">
|
||||
<em class="property">class </em><code class="sig-prename descclassname">royalnet.commands.</code><code class="sig-name descname">Command</code><span class="sig-paren">(</span><em class="sig-param">interface: royalnet.commands.commandinterface.CommandInterface</em><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.commands.Command" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="attribute">
|
||||
<dt id="royalnet.commands.Command.aliases">
|
||||
<code class="sig-name descname">aliases</code><em class="property"> = NotImplemented</em><a class="headerlink" href="#royalnet.commands.Command.aliases" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>A list of possible aliases for a command.
|
||||
To have <code class="docutils literal notranslate"><span class="pre">/e</span></code> as alias for <code class="docutils literal notranslate"><span class="pre">/example</span></code>, one should set aliases to <code class="docutils literal notranslate"><span class="pre">["e"]</span></code>.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="attribute">
|
||||
<dt id="royalnet.commands.Command.description">
|
||||
<code class="sig-name descname">description</code><em class="property"> = NotImplemented</em><a class="headerlink" href="#royalnet.commands.Command.description" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>A small description of the command, to be displayed when the command is being autocompleted.</p>
|
||||
|
@ -683,7 +685,7 @@ To have <code class="docutils literal notranslate"><span class="pre">/example</s
|
|||
<dl class="attribute">
|
||||
<dt id="royalnet.commands.Command.syntax">
|
||||
<code class="sig-name descname">syntax</code><em class="property"> = ''</em><a class="headerlink" href="#royalnet.commands.Command.syntax" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>The syntax of the command, to be displayed when a <a class="reference internal" href="#royalnet.error.InvalidInputError" title="royalnet.error.InvalidInputError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">royalnet.error.InvalidInputError</span></code></a> is raised,
|
||||
<dd><p>The syntax of the command, to be displayed when a <code class="xref py py-exc docutils literal notranslate"><span class="pre">royalnet.error.InvalidInputError</span></code> is raised,
|
||||
in the format <code class="docutils literal notranslate"><span class="pre">(required_arg)</span> <span class="pre">[optional_arg]</span></code>.</p>
|
||||
</dd></dl>
|
||||
|
||||
|
@ -694,12 +696,12 @@ in the format <code class="docutils literal notranslate"><span class="pre">(requ
|
|||
<em class="property">class </em><code class="sig-prename descclassname">royalnet.commands.</code><code class="sig-name descname">CommandData</code><a class="headerlink" href="#royalnet.commands.CommandData" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="method">
|
||||
<dt id="royalnet.commands.CommandData.delete_invoking">
|
||||
<em class="property">async </em><code class="sig-name descname">delete_invoking</code><span class="sig-paren">(</span><em class="sig-param">error_if_unavailable=False</em><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.commands.CommandData.delete_invoking" title="Permalink to this definition">¶</a></dt>
|
||||
<em class="property">async </em><code class="sig-name descname">delete_invoking</code><span class="sig-paren">(</span><em class="sig-param">error_if_unavailable=False</em><span class="sig-paren">)</span> → None<a class="headerlink" href="#royalnet.commands.CommandData.delete_invoking" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Delete the invoking message, if supported by the interface.</p>
|
||||
<p>The invoking message is the message send by the user that contains the command.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>error_if_unavailable</strong> – if True, raise NotImplementedError() if the message cannot been deleted</p>
|
||||
<dd class="field-odd"><p><strong>error_if_unavailable</strong> – if True, raise an exception if the message cannot been deleted.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
@ -711,10 +713,7 @@ in the format <code class="docutils literal notranslate"><span class="pre">(requ
|
|||
That probably means, the database row identifying the user.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>error_if_none</strong> – Raise a <a class="reference internal" href="#royalnet.error.UnregisteredError" title="royalnet.error.UnregisteredError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">royalnet.error.UnregisteredError</span></code></a> if this is True and the call has no author.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Raises</dt>
|
||||
<dd class="field-even"><p><a class="reference internal" href="#royalnet.error.UnregisteredError" title="royalnet.error.UnregisteredError"><strong>royalnet.error.UnregisteredError</strong></a> – </p>
|
||||
<dd class="field-odd"><p><strong>error_if_none</strong> – Raise an exception if this is True and the call has no author.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
@ -749,7 +748,7 @@ That probably means, the database row identifying the user.</p>
|
|||
<dd><p>Arguments can be accessed with an array notation, such as <code class="docutils literal notranslate"><span class="pre">args[0]</span></code>.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Raises</dt>
|
||||
<dd class="field-odd"><p><a class="reference internal" href="#royalnet.error.InvalidInputError" title="royalnet.error.InvalidInputError"><strong>royalnet.error.InvalidInputError</strong></a> – if the requested argument does not exist.</p>
|
||||
<dd class="field-odd"><p><strong>royalnet.error.InvalidInputError</strong> – if the requested argument does not exist.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
@ -760,10 +759,10 @@ That probably means, the database row identifying the user.</p>
|
|||
<dd><p>Get the arguments as a space-joined string.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>require_at_least</strong> – the minimum amount of arguments required, will raise <a class="reference internal" href="#royalnet.error.InvalidInputError" title="royalnet.error.InvalidInputError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">royalnet.error.InvalidInputError</span></code></a> if the requirement is not fullfilled.</p>
|
||||
<dd class="field-odd"><p><strong>require_at_least</strong> – the minimum amount of arguments required, will raise <code class="xref py py-exc docutils literal notranslate"><span class="pre">royalnet.error.InvalidInputError</span></code> if the requirement is not fullfilled.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Raises</dt>
|
||||
<dd class="field-even"><p><a class="reference internal" href="#royalnet.error.InvalidInputError" title="royalnet.error.InvalidInputError"><strong>royalnet.error.InvalidInputError</strong></a> – if there are less than <code class="docutils literal notranslate"><span class="pre">require_at_least</span></code> arguments.</p>
|
||||
<dd class="field-even"><p><strong>royalnet.error.InvalidInputError</strong> – if there are less than <code class="docutils literal notranslate"><span class="pre">require_at_least</span></code> arguments.</p>
|
||||
</dd>
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>The space-joined string.</p>
|
||||
|
@ -780,7 +779,7 @@ That probably means, the database row identifying the user.</p>
|
|||
<dd class="field-odd"><p><strong>pattern</strong> – The regex pattern to be passed to <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>.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Raises</dt>
|
||||
<dd class="field-even"><p><a class="reference internal" href="#royalnet.error.InvalidInputError" title="royalnet.error.InvalidInputError"><strong>royalnet.error.InvalidInputError</strong></a> – if the pattern doesn’t match.</p>
|
||||
<dd class="field-even"><p><strong>royalnet.error.InvalidInputError</strong> – if the pattern doesn’t match.</p>
|
||||
</dd>
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>The matched groups, as returned by <code class="xref py py-func docutils literal notranslate"><span class="pre">re.Match.groups()</span></code>.</p>
|
||||
|
@ -807,6 +806,33 @@ That probably means, the database row identifying the user.</p>
|
|||
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.commands.CommandError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.commands.</code><code class="sig-name descname">CommandError</code><span class="sig-paren">(</span><em class="sig-param">message=''</em><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.commands.CommandError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Something went wrong during the execution of this command.</p>
|
||||
<p>Display an error message to the user, explaining what went wrong.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.commands.InvalidInputError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.commands.</code><code class="sig-name descname">InvalidInputError</code><span class="sig-paren">(</span><em class="sig-param">message=''</em><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.commands.InvalidInputError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>The command has received invalid input and cannot complete.</p>
|
||||
<p>Display an error message to the user, along with the correct syntax for the command.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.commands.UnsupportedError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.commands.</code><code class="sig-name descname">UnsupportedError</code><span class="sig-paren">(</span><em class="sig-param">message=''</em><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.commands.UnsupportedError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>A requested feature is not available on this interface.</p>
|
||||
<p>Display an error message to the user, telling them to use another interface.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.commands.KeyboardExpiredError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.commands.</code><code class="sig-name descname">KeyboardExpiredError</code><span class="sig-paren">(</span><em class="sig-param">message=''</em><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.commands.KeyboardExpiredError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>A special type of exception that can be raised in keyboard handlers to mark a specific keyboard as expired.</p>
|
||||
</dd></dl>
|
||||
|
||||
</div>
|
||||
<div class="section" id="module-royalnet.database">
|
||||
<span id="database"></span><h2>Database<a class="headerlink" href="#module-royalnet.database" title="Permalink to this headline">¶</a></h2>
|
||||
|
@ -896,7 +922,7 @@ That probably means, the database row identifying the user.</p>
|
|||
|
||||
<dl class="method">
|
||||
<dt id="royalnet.network.NetworkLink.run">
|
||||
<em class="property">async </em><code class="sig-name descname">run</code><span class="sig-paren">(</span><em class="sig-param">loops: numbers.Real = inf</em><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.network.NetworkLink.run" title="Permalink to this definition">¶</a></dt>
|
||||
<em class="property">async </em><code class="sig-name descname">run</code><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.network.NetworkLink.run" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Blockingly run the Link.</p>
|
||||
</dd></dl>
|
||||
|
||||
|
@ -1031,19 +1057,14 @@ Contains info about the source and the destination.</p>
|
|||
<dd><p>Executed every time a package is received and must be routed somewhere.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="method">
|
||||
<dt id="royalnet.network.NetworkServer.run">
|
||||
<em class="property">async </em><code class="sig-name descname">run</code><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.network.NetworkServer.run" title="Permalink to this definition">¶</a></dt>
|
||||
<dd></dd></dl>
|
||||
|
||||
<dl class="method">
|
||||
<dt id="royalnet.network.NetworkServer.run_blocking">
|
||||
<code class="sig-name descname">run_blocking</code><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.network.NetworkServer.run_blocking" title="Permalink to this definition">¶</a></dt>
|
||||
<code class="sig-name descname">run_blocking</code><span class="sig-paren">(</span><em class="sig-param">verbose=False</em><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.network.NetworkServer.run_blocking" title="Permalink to this definition">¶</a></dt>
|
||||
<dd></dd></dl>
|
||||
|
||||
<dl class="method">
|
||||
<dt id="royalnet.network.NetworkServer.serve">
|
||||
<em class="property">async </em><code class="sig-name descname">serve</code><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.network.NetworkServer.serve" title="Permalink to this definition">¶</a></dt>
|
||||
<code class="sig-name descname">serve</code><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.network.NetworkServer.serve" title="Permalink to this definition">¶</a></dt>
|
||||
<dd></dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
|
@ -1347,48 +1368,6 @@ Use with caution?</p>
|
|||
</div>
|
||||
<div class="section" id="module-royalnet.error">
|
||||
<span id="error"></span><h2>Error<a class="headerlink" href="#module-royalnet.error" title="Permalink to this headline">¶</a></h2>
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.error.CurrentlyDisabledError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.error.</code><code class="sig-name descname">CurrentlyDisabledError</code><a class="headerlink" href="#royalnet.error.CurrentlyDisabledError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>This feature is temporarely disabled and is not available right now.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.error.ExternalError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.error.</code><code class="sig-name descname">ExternalError</code><a class="headerlink" href="#royalnet.error.ExternalError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Something went wrong in a non-Royalnet component and the command execution cannot be completed.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.error.FileTooBigError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.error.</code><code class="sig-name descname">FileTooBigError</code><a class="headerlink" href="#royalnet.error.FileTooBigError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>The file to be downloaded would be too big to store; therefore, it has been skipped.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.error.InvalidConfigError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.error.</code><code class="sig-name descname">InvalidConfigError</code><a class="headerlink" href="#royalnet.error.InvalidConfigError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>The bot has not been configured correctly, therefore the command can not function.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.error.InvalidInputError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.error.</code><code class="sig-name descname">InvalidInputError</code><a class="headerlink" href="#royalnet.error.InvalidInputError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>The command has received invalid input and cannot complete.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.error.KeyboardExpiredError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.error.</code><code class="sig-name descname">KeyboardExpiredError</code><a class="headerlink" href="#royalnet.error.KeyboardExpiredError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>A special type of exception that can be raised in keyboard handlers to mark a specific keyboard as expired.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.error.NoneFoundError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.error.</code><code class="sig-name descname">NoneFoundError</code><a class="headerlink" href="#royalnet.error.NoneFoundError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>The element that was being looked for was not found.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.error.RoyalnetRequestError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.error.</code><code class="sig-name descname">RoyalnetRequestError</code><span class="sig-paren">(</span><em class="sig-param">error: ResponseError</em><span class="sig-paren">)</span><a class="headerlink" href="#royalnet.error.RoyalnetRequestError" title="Permalink to this definition">¶</a></dt>
|
||||
|
@ -1407,24 +1386,6 @@ Use with caution?</p>
|
|||
<dd><p>The <a class="reference internal" href="#royalnet.network.Response" title="royalnet.network.Response"><code class="xref py py-class docutils literal notranslate"><span class="pre">royalnet.network.Response</span></code></a> that was received is invalid.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.error.TooManyFoundError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.error.</code><code class="sig-name descname">TooManyFoundError</code><a class="headerlink" href="#royalnet.error.TooManyFoundError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Multiple elements matching the request were found, and only one was expected.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.error.UnregisteredError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.error.</code><code class="sig-name descname">UnregisteredError</code><a class="headerlink" href="#royalnet.error.UnregisteredError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>The command required a registered user, and the user was not registered.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="exception">
|
||||
<dt id="royalnet.error.UnsupportedError">
|
||||
<em class="property">exception </em><code class="sig-prename descclassname">royalnet.error.</code><code class="sig-name descname">UnsupportedError</code><a class="headerlink" href="#royalnet.error.UnsupportedError" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>The command is not supported for the specified interface.</p>
|
||||
</dd></dl>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
<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>
|
||||
|
@ -256,7 +257,7 @@ ones.</p>
|
|||
<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
|
||||
<a class="reference internal" href="apireference.html#royalnet.error.InvalidInputError" title="royalnet.error.InvalidInputError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">royalnet.error.InvalidInputError</span></code></a> is raised, making the arguments accessed in this way <strong>required</strong>.</p>
|
||||
<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>
|
||||
|
||||
|
@ -295,7 +296,7 @@ will return <code class="xref py py-const docutils literal notranslate"><span cl
|
|||
<span class="c1"># "carbonara al-dente"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can specify a minimum number of arguments too, so that an <a class="reference internal" href="apireference.html#royalnet.error.InvalidInputError" title="royalnet.error.InvalidInputError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">royalnet.error.InvalidInputError</span></code></a> will be
|
||||
<p>You can specify a minimum number of arguments too, so that an <a class="reference internal" href="apireference.html#royalnet.commands.InvalidInputError" title="royalnet.commands.InvalidInputError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">InvalidInputError</span></code></a> 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>
|
||||
|
@ -306,7 +307,7 @@ raised if not enough arguments are present:</p>
|
|||
<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 <a class="reference internal" href="apireference.html#royalnet.commands.CommandArgs.match" title="royalnet.commands.CommandArgs.match"><code class="xref py py-meth docutils literal notranslate"><span class="pre">CommandArgs.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 <a class="reference internal" href="apireference.html#royalnet.error.InvalidInputError" title="royalnet.error.InvalidInputError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">royalnet.error.InvalidInputError</span></code></a> if there is no match.</p>
|
||||
which returns a tuple of the matched groups and raises an <a class="reference internal" href="apireference.html#royalnet.commands.InvalidInputError" title="royalnet.commands.InvalidInputError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">InvalidInputError</span></code></a> 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>
|
||||
|
@ -323,6 +324,25 @@ which returns a tuple of the matched groups and raises an <a class="reference in
|
|||
</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 <a class="reference internal" href="apireference.html#royalnet.commands.CommandError" title="royalnet.commands.CommandError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">CommandError</span></code></a> 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 <a class="reference internal" href="apireference.html#royalnet.commands.InvalidInputError" title="royalnet.commands.InvalidInputError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">InvalidInputError</span></code></a> 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
|
||||
<a class="reference internal" href="apireference.html#royalnet.commands.UnsupportedError" title="royalnet.commands.UnsupportedError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">UnsupportedError</span></code></a> 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>
|
||||
|
|
|
@ -155,7 +155,6 @@
|
|||
| <a href="#B"><strong>B</strong></a>
|
||||
| <a href="#C"><strong>C</strong></a>
|
||||
| <a href="#D"><strong>D</strong></a>
|
||||
| <a href="#E"><strong>E</strong></a>
|
||||
| <a href="#F"><strong>F</strong></a>
|
||||
| <a href="#G"><strong>G</strong></a>
|
||||
| <a href="#H"><strong>H</strong></a>
|
||||
|
@ -260,10 +259,12 @@
|
|||
<li><a href="apireference.html#royalnet.bots.DiscordBot.advance_music_data">advance_music_data() (royalnet.bots.DiscordBot method)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.database.Alchemy">Alchemy (class in royalnet.database)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.commands.CommandInterface.alchemy">alchemy (royalnet.commands.CommandInterface attribute)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="apireference.html#royalnet.commands.CommandInterface.alchemy">alchemy (royalnet.commands.CommandInterface attribute)</a>
|
||||
<li><a href="apireference.html#royalnet.commands.Command.aliases">aliases (royalnet.commands.Command attribute)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.utils.andformat">andformat() (in module royalnet.utils)</a>
|
||||
</li>
|
||||
|
@ -292,25 +293,23 @@
|
|||
<li><a href="apireference.html#royalnet.commands.CommandArgs">CommandArgs (class in royalnet.commands)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.commands.CommandData">CommandData (class in royalnet.commands)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.commands.CommandError">CommandError</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.commands.CommandInterface">CommandInterface (class in royalnet.commands)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.network.NetworkLink.connect">connect() (royalnet.network.NetworkLink method)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.network.ConnectionClosedError">ConnectionClosedError</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="apireference.html#royalnet.network.ConnectionClosedError">ConnectionClosedError</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.audio.YtdlMp3.convert_to_mp3">convert_to_mp3() (royalnet.audio.YtdlMp3 method)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.audio.YtdlDiscord.convert_to_pcm">convert_to_pcm() (royalnet.audio.YtdlDiscord method)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.audio.YtdlDiscord.create_and_ready_from_url">create_and_ready_from_url() (royalnet.audio.YtdlDiscord class method)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="apireference.html#royalnet.audio.YtdlMp3.create_and_ready_from_url">(royalnet.audio.YtdlMp3 class method)</a>
|
||||
<li><a href="apireference.html#royalnet.audio.YtdlMp3.create_and_ready_from_url">create_and_ready_from_url() (royalnet.audio.YtdlMp3 class method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="apireference.html#royalnet.web.create_app">create_app() (in module royalnet.web)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.audio.YtdlDiscord.create_from_url">create_from_url() (royalnet.audio.YtdlDiscord class method)</a>
|
||||
|
@ -319,8 +318,6 @@
|
|||
<li><a href="apireference.html#royalnet.audio.YtdlMp3.create_from_url">(royalnet.audio.YtdlMp3 class method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="apireference.html#royalnet.error.CurrentlyDisabledError">CurrentlyDisabledError</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
|
@ -354,22 +351,12 @@
|
|||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="E">E</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="apireference.html#royalnet.error.ExternalError">ExternalError</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="F">F</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="apireference.html#royalnet.audio.FileAudioSource">FileAudioSource (class in royalnet.audio)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.utils.fileformat">fileformat() (in module royalnet.utils)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.error.FileTooBigError">FileTooBigError</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.network.NetworkServer.find_client">find_client() (royalnet.network.NetworkServer method)</a>
|
||||
</li>
|
||||
|
@ -435,9 +422,7 @@
|
|||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="apireference.html#royalnet.error.InvalidConfigError">InvalidConfigError</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.error.InvalidInputError">InvalidInputError</a>
|
||||
<li><a href="apireference.html#royalnet.commands.InvalidInputError">InvalidInputError</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.audio.YtdlFile.is_downloaded">is_downloaded() (royalnet.audio.YtdlFile method)</a>
|
||||
</li>
|
||||
|
@ -461,7 +446,7 @@
|
|||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="apireference.html#royalnet.error.KeyboardExpiredError">KeyboardExpiredError</a>
|
||||
<li><a href="apireference.html#royalnet.commands.KeyboardExpiredError">KeyboardExpiredError</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
@ -504,16 +489,14 @@
|
|||
<li><a href="apireference.html#royalnet.network.NetworkConfig">NetworkConfig (class in royalnet.network)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.network.NetworkError">NetworkError</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.utils.NetworkHandler">NetworkHandler (class in royalnet.utils)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="apireference.html#royalnet.utils.NetworkHandler">NetworkHandler (class in royalnet.utils)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.network.NetworkLink">NetworkLink (class in royalnet.network)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.network.NetworkServer">NetworkServer (class in royalnet.network)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.error.NoneFoundError">NoneFoundError</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.network.NotConnectedError">NotConnectedError</a>
|
||||
</li>
|
||||
|
@ -643,8 +626,6 @@
|
|||
<li><a href="apireference.html#royalnet.commands.Command.run">(royalnet.commands.Command method)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.network.NetworkLink.run">(royalnet.network.NetworkLink method)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.network.NetworkServer.run">(royalnet.network.NetworkServer method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="apireference.html#royalnet.bots.GenericBot.run_blocking">run_blocking() (royalnet.bots.GenericBot method)</a>
|
||||
|
@ -708,8 +689,6 @@
|
|||
<li><a href="apireference.html#royalnet.network.Package.to_json_bytes">to_json_bytes() (royalnet.network.Package method)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.network.Package.to_json_string">to_json_string() (royalnet.network.Package method)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.error.TooManyFoundError">TooManyFoundError</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
@ -720,12 +699,10 @@
|
|||
<li><a href="apireference.html#royalnet.commands.CommandInterface.unregister_keyboard_key">unregister_keyboard_key() (royalnet.commands.CommandInterface method)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.commands.CommandInterface.unregister_net_handler">unregister_net_handler() (royalnet.commands.CommandInterface method)</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.error.UnregisteredError">UnregisteredError</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="apireference.html#royalnet.error.UnsupportedError">UnsupportedError</a>
|
||||
<li><a href="apireference.html#royalnet.commands.UnsupportedError">UnsupportedError</a>
|
||||
</li>
|
||||
<li><a href="apireference.html#royalnet.bots.DiscordBot.update_activity_with_source_title">update_activity_with_source_title() (royalnet.bots.DiscordBot method)</a>
|
||||
</li>
|
||||
|
|
|
@ -153,7 +153,11 @@
|
|||
<p>Welcome to the documentation of Royalnet!</p>
|
||||
<div class="toctree-wrapper compound">
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="runningroyalnet.html">Running Royalnet</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="runningroyalnet.html">Running Royalnet</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="runningroyalnet.html#the-keyring">The Keyring</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="runningroyalnet.html#running-the-bots">Running the bots</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="creatingacommand.html">Royalnet Commands</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="creatingacommand.html#creating-a-new-command">Creating a new Command</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="creatingacommand.html#command-arguments">Command arguments</a><ul>
|
||||
|
@ -163,6 +167,7 @@
|
|||
<li class="toctree-l3"><a class="reference internal" href="creatingacommand.html#regular-expressions">Regular expressions</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="creatingacommand.html#raising-errors">Raising errors</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="creatingacommand.html#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="creatingacommand.html#coroutines-and-slow-operations">Coroutines and slow operations</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="creatingacommand.html#delete-the-invoking-message">Delete the invoking message</a></li>
|
||||
|
|
Binary file not shown.
|
@ -82,7 +82,11 @@
|
|||
|
||||
|
||||
<ul class="current">
|
||||
<li class="toctree-l1 current"><a class="current reference internal" href="#">Running Royalnet</a></li>
|
||||
<li class="toctree-l1 current"><a class="current reference internal" href="#">Running Royalnet</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#the-keyring">The Keyring</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#running-the-bots">Running the bots</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="creatingacommand.html">Royalnet Commands</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="apireference.html">API Reference</a></li>
|
||||
</ul>
|
||||
|
@ -151,7 +155,56 @@
|
|||
|
||||
<div class="section" id="running-royalnet">
|
||||
<h1>Running Royalnet<a class="headerlink" href="#running-royalnet" title="Permalink to this headline">¶</a></h1>
|
||||
<p>This documentation page hasn’t been written yet, please refer to the README until then.</p>
|
||||
<p>To run a <code class="docutils literal notranslate"><span class="pre">royalnet</span></code> instance, you have first to download the package from <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
<div class="section" id="the-keyring">
|
||||
<h2>The Keyring<a class="headerlink" href="#the-keyring" title="Permalink to this headline">¶</a></h2>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="n">royalnet</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>To run <code class="docutils literal notranslate"><span class="pre">royalnet</span></code>, you’ll have to setup the system keyring.</p>
|
||||
<p>On Windows and desktop Linux, this is already configured;
|
||||
on a headless Linux instance, you’ll need to <a class="reference external" href="https://keyring.readthedocs.io/en/latest/#using-keyring-on-headless-linux-systems">manually start and unlock the keyring daemon</a>.</p>
|
||||
<p>Now you have to create a new <code class="docutils literal notranslate"><span class="pre">royalnet</span></code> configuration. Start the configuration wizard:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">python</span> <span class="o">-</span><span class="n">m</span> <span class="n">royalnet</span><span class="o">.</span><span class="n">configurator</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You’ll be prompted to enter a “secrets name”: this is the name of the group of API keys that will be associated with
|
||||
your bot. Enter a name that you’ll be able to remember.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Desired</span> <span class="n">secrets</span> <span class="n">name</span> <span class="p">[</span><span class="n">__default__</span><span class="p">]:</span> <span class="n">royalgames</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You’ll then be asked for a network password.</p>
|
||||
<p>This password is used to connect to the rest of the <a class="reference internal" href="apireference.html#module-royalnet.network" title="royalnet.network"><code class="xref py py-mod docutils literal notranslate"><span class="pre">royalnet.network</span></code></a>, or, if you’re hosting a local Network,
|
||||
it will be the necessary password to connect to it:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Network</span> <span class="n">password</span> <span class="p">[]:</span> <span class="n">cosafaunapesuunafoglia</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Then you’ll be asked for a Telegram Bot API token.
|
||||
You can get one from <a class="reference external" href="https://t.me/BotFather">@BotFather</a>.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Telegram</span> <span class="n">Bot</span> <span class="n">API</span> <span class="n">token</span> <span class="p">[]:</span> <span class="mi">000000000</span><span class="p">:</span><span class="n">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The next prompt will ask for a Discord Bot API token.
|
||||
You can get one at the <a class="reference external" href="https://discordapp.com/developers/applications/">Discord Developers Portal</a>.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Discord</span> <span class="n">Bot</span> <span class="n">API</span> <span class="n">token</span> <span class="p">[]:</span> <span class="n">AAAAAAAAAAAAAAAAAAAAAAAA</span><span class="o">.</span><span class="n">AAAAAA</span><span class="o">.</span><span class="n">AAAAAAAAAAAAAAAAAAAAAAAAAAA</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Now the configurator will ask you for a Imgur API token.
|
||||
<a class="reference external" href="https://api.imgur.com/oauth2/addclient">Register an application</a> on Imgur to be supplied one.
|
||||
The token should be of type “anonymous usage without user authorization”.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Imgur</span> <span class="n">API</span> <span class="n">token</span> <span class="p">[]:</span> <span class="n">aaaaaaaaaaaaaaa</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Next, you’ll be asked for a Sentry DSN. You probably won’t have one, so just ignore it and press enter.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Sentry</span> <span class="n">DSN</span> <span class="p">[]:</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Now that all tokens are configured, you’re ready to launch the bot!</p>
|
||||
</div>
|
||||
<div class="section" id="running-the-bots">
|
||||
<h2>Running the bots<a class="headerlink" href="#running-the-bots" title="Permalink to this headline">¶</a></h2>
|
||||
<p>TODO</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -137,7 +137,7 @@ If you want the full argument string, you can use the :py:meth:`CommandArgs.join
|
|||
args.joined()
|
||||
# "carbonara al-dente"
|
||||
|
||||
You can specify a minimum number of arguments too, so that an :py:exc:`royalnet.error.InvalidInputError` will be
|
||||
You can specify a minimum number of arguments too, so that an :py:exc:`InvalidInputError` will be
|
||||
raised if not enough arguments are present: ::
|
||||
|
||||
args.joined(require_at_least=3)
|
||||
|
@ -149,7 +149,7 @@ Regular expressions
|
|||
For more complex commands, you may want to get arguments through `regular expressions <https://regexr.com/>`_.
|
||||
|
||||
You can then use the :py: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 :py:exc:`royalnet.error.InvalidInputError` if there is no match.
|
||||
which returns a tuple of the matched groups and raises an :py:exc:`InvalidInputError` if there is no match.
|
||||
|
||||
To match a pattern, :py:func:`re.match` is used, meaning that Python will try to match only at the beginning of the string. ::
|
||||
|
||||
|
@ -165,6 +165,25 @@ To match a pattern, :py:func:`re.match` is used, meaning that Python will try to
|
|||
args.match(r"\s*(carb\w+)\s*(al-\w+)")
|
||||
# ("carbonara", "al-dente")
|
||||
|
||||
Raising errors
|
||||
---------------------------------------------
|
||||
|
||||
If you want to display an error message to the user, you can raise a :py:exc:`CommandError` using the error message as argument: ::
|
||||
|
||||
if not kitchen.is_open():
|
||||
raise CommandError("The kitchen is closed. Come back later!")
|
||||
|
||||
You can also manually raise :py:exc:`InvalidInputError` to redisplay the command syntax, along with your error message: ::
|
||||
|
||||
if args[0] not in allowed_pasta:
|
||||
raise InvalidInputError("The specified pasta type is invalid.")
|
||||
|
||||
If you need a Royalnet feature that's not available on the current interface, you can raise an
|
||||
:py:exc:`UnsupportedError` with a brief description of what's missing: ::
|
||||
|
||||
if interface.name != "telegram":
|
||||
raise UnsupportedError("This command can only be run on Telegram interfaces.")
|
||||
|
||||
Running code at the initialization of the bot
|
||||
---------------------------------------------
|
||||
|
||||
|
|
|
@ -3,4 +3,60 @@
|
|||
Running Royalnet
|
||||
====================================
|
||||
|
||||
This documentation page hasn't been written yet, please refer to the README until then.
|
||||
To run a ``royalnet`` instance, you have first to download the package from ``pip``:
|
||||
|
||||
The Keyring
|
||||
------------------------------------
|
||||
::
|
||||
|
||||
pip install royalnet
|
||||
|
||||
|
||||
To run ``royalnet``, you'll have to setup the system keyring.
|
||||
|
||||
On Windows and desktop Linux, this is already configured;
|
||||
on a headless Linux instance, you'll need to `manually start and unlock the keyring daemon
|
||||
<https://keyring.readthedocs.io/en/latest/#using-keyring-on-headless-linux-systems>`_.
|
||||
|
||||
Now you have to create a new ``royalnet`` configuration. Start the configuration wizard: ::
|
||||
|
||||
python -m royalnet.configurator
|
||||
|
||||
You'll be prompted to enter a "secrets name": this is the name of the group of API keys that will be associated with
|
||||
your bot. Enter a name that you'll be able to remember. ::
|
||||
|
||||
Desired secrets name [__default__]: royalgames
|
||||
|
||||
You'll then be asked for a network password.
|
||||
|
||||
This password is used to connect to the rest of the :py:mod:`royalnet.network`, or, if you're hosting a local Network,
|
||||
it will be the necessary password to connect to it: ::
|
||||
|
||||
Network password []: cosafaunapesuunafoglia
|
||||
|
||||
Then you'll be asked for a Telegram Bot API token.
|
||||
You can get one from `@BotFather <https://t.me/BotFather>`_. ::
|
||||
|
||||
Telegram Bot API token []: 000000000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
||||
The next prompt will ask for a Discord Bot API token.
|
||||
You can get one at the `Discord Developers Portal <https://discordapp.com/developers/applications/>`_. ::
|
||||
|
||||
Discord Bot API token []: AAAAAAAAAAAAAAAAAAAAAAAA.AAAAAA.AAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
||||
Now the configurator will ask you for a Imgur API token.
|
||||
`Register an application <https://api.imgur.com/oauth2/addclient>`_ on Imgur to be supplied one.
|
||||
The token should be of type "anonymous usage without user authorization". ::
|
||||
|
||||
Imgur API token []: aaaaaaaaaaaaaaa
|
||||
|
||||
Next, you'll be asked for a Sentry DSN. You probably won't have one, so just ignore it and press enter. ::
|
||||
|
||||
Sentry DSN []:
|
||||
|
||||
Now that all tokens are configured, you're ready to launch the bot!
|
||||
|
||||
Running the bots
|
||||
------------------------------------
|
||||
|
||||
TODO
|
|
@ -52,7 +52,7 @@ class DiscordBot(GenericBot):
|
|||
query = query.filter(self.identity_column == user.id)
|
||||
result = await asyncify(query.one_or_none)
|
||||
if result is None and error_if_none:
|
||||
raise UnregisteredError("Author is not registered")
|
||||
raise CommandError("You must be registered to use this command.")
|
||||
return result
|
||||
|
||||
async def delete_invoking(data, error_if_unavailable=False):
|
||||
|
@ -115,52 +115,42 @@ class DiscordBot(GenericBot):
|
|||
# Call the command
|
||||
log.debug(f"Calling command '{command.name}'")
|
||||
with message.channel.typing():
|
||||
# Run the command
|
||||
try:
|
||||
await command.run(CommandArgs(parameters), data=data)
|
||||
await command.run(CommandArgs(parameters), data)
|
||||
except InvalidInputError as e:
|
||||
await data.reply(f":warning: {' '.join(e.args)}\n"
|
||||
f"Syntax: [c]!{command.name} {command.syntax}[/c]")
|
||||
await data.reply(f"⚠️ {e.message}\n"
|
||||
f"Syntax: [c]/{command.name} {command.syntax}[/c]")
|
||||
except UnsupportedError as e:
|
||||
await data.reply(f"⚠️ {e.message}")
|
||||
except CommandError as e:
|
||||
await data.reply(f"⚠️ {e.message}")
|
||||
except Exception as e:
|
||||
sentry_sdk.capture_exception(e)
|
||||
error_message = f"🦀 {e.__class__.__name__} 🦀\n"
|
||||
error_message = f"🦀 [b]{e.__class__.__name__}[/b] 🦀\n"
|
||||
error_message += '\n'.join(e.args)
|
||||
log.error(f"Error in {command.name}: {error_message}")
|
||||
await data.reply(f"{error_message}")
|
||||
if __debug__:
|
||||
raise
|
||||
await data.reply(error_message)
|
||||
|
||||
async def on_ready(cli):
|
||||
async def on_ready(cli) -> None:
|
||||
log.debug("Connection successful, client is ready")
|
||||
await cli.change_presence(status=discord.Status.online)
|
||||
|
||||
def find_guild_by_name(cli, name: str) -> discord.Guild:
|
||||
"""Find the :py:class:`discord.Guild` with the specified name. Case-insensitive.
|
||||
|
||||
Raises:
|
||||
:py:exc:`NoneFoundError` if no channels are found.
|
||||
:py:exc:`TooManyFoundError` if more than one is found."""
|
||||
def find_guild_by_name(cli, name: str) -> typing.List[discord.Guild]:
|
||||
"""Find the :py:class:`discord.Guild` with the specified name (case insensitive)."""
|
||||
all_guilds: typing.List[discord.Guild] = cli.guilds
|
||||
matching_channels: typing.List[discord.Guild] = []
|
||||
for guild in all_guilds:
|
||||
if guild.name.lower() == name.lower():
|
||||
matching_channels.append(guild)
|
||||
if len(matching_channels) == 0:
|
||||
raise NoneFoundError("No channels were found")
|
||||
elif len(matching_channels) > 1:
|
||||
raise TooManyFoundError("Too many channels were found")
|
||||
return matching_channels[0]
|
||||
return matching_channels
|
||||
|
||||
def find_channel_by_name(cli,
|
||||
name: str,
|
||||
guild: typing.Optional[discord.Guild] = None) -> discord.abc.GuildChannel:
|
||||
guild: typing.Optional[discord.Guild] = None) -> typing.List[discord.abc.GuildChannel]:
|
||||
"""Find the :py:class:`TextChannel`, :py:class:`VoiceChannel` or :py:class:`CategoryChannel` with the
|
||||
specified name.
|
||||
specified name (case insensitive).
|
||||
|
||||
Case-insensitive.
|
||||
|
||||
Guild is optional, but the method will raise a :py:exc:`TooManyFoundError` if none is specified and
|
||||
there is more than one channel with the same name. Will also raise a :py:exc:`NoneFoundError` if no
|
||||
channels are found. """
|
||||
You can specify a guild to only find channels in that specific guild."""
|
||||
if guild is not None:
|
||||
all_channels = guild.channels
|
||||
else:
|
||||
|
@ -173,21 +163,14 @@ class DiscordBot(GenericBot):
|
|||
continue
|
||||
if channel.name.lower() == name.lower():
|
||||
matching_channels.append(channel)
|
||||
if len(matching_channels) == 0:
|
||||
raise NoneFoundError("No channels were found")
|
||||
elif len(matching_channels) > 1:
|
||||
raise TooManyFoundError("Too many channels were found")
|
||||
return matching_channels[0]
|
||||
return matching_channels
|
||||
|
||||
def find_voice_client_by_guild(cli, guild: discord.Guild):
|
||||
"""Find the :py:class:`discord.VoiceClient` belonging to a specific :py:class:`discord.Guild`.
|
||||
|
||||
Raises:
|
||||
:py:exc:`NoneFoundError` if the :py:class:`discord.Guild` currently has no :py:class:`discord.VoiceClient`."""
|
||||
def find_voice_client_by_guild(cli, guild: discord.Guild) -> typing.Optional[discord.VoiceClient]:
|
||||
"""Find the :py:class:`discord.VoiceClient` belonging to a specific :py:class:`discord.Guild`."""
|
||||
for voice_client in cli.voice_clients:
|
||||
if voice_client.guild == guild:
|
||||
return voice_client
|
||||
raise NoneFoundError("No voice clients found")
|
||||
return None
|
||||
|
||||
return DiscordClient
|
||||
|
||||
|
|
|
@ -103,18 +103,15 @@ class GenericBot:
|
|||
log.debug(f"Using {network_handler} as handler for {request.handler}")
|
||||
response: Response = await getattr(network_handler, self.interface_name)(self, request.data)
|
||||
return response.to_dict()
|
||||
except Exception:
|
||||
if __debug__:
|
||||
raise
|
||||
exit(1)
|
||||
_, exc, _ = sys.exc_info()
|
||||
log.debug(f"Exception {exc} in {network_handler}")
|
||||
except Exception as e:
|
||||
sentry_sdk.capture_exception(e)
|
||||
log.debug(f"Exception {e} in {network_handler}")
|
||||
return ResponseError("exception_in_handler",
|
||||
f"An exception was raised in {network_handler} for {request.handler}. Check "
|
||||
f"extra_info for details.",
|
||||
extra_info={
|
||||
"type": exc.__class__.__name__,
|
||||
"str": str(exc)
|
||||
"type": e.__class__.__name__,
|
||||
"str": str(e)
|
||||
}).to_dict()
|
||||
|
||||
def _init_database(self):
|
||||
|
@ -127,8 +124,8 @@ class GenericBot:
|
|||
required_tables = required_tables.union(command.require_alchemy_tables)
|
||||
log.debug(f"Found {len(required_tables)} required tables")
|
||||
self.alchemy = Alchemy(self.uninitialized_database_config.database_uri, required_tables)
|
||||
self.master_table = self.alchemy.__getattribute__(self.uninitialized_database_config.master_table.__name__)
|
||||
self.identity_table = self.alchemy.__getattribute__(self.uninitialized_database_config.identity_table.__name__)
|
||||
self.master_table = self.alchemy.__getattribute__(self.uninitialized_database_config.master_table.__qualname__)
|
||||
self.identity_table = self.alchemy.__getattribute__(self.uninitialized_database_config.identity_table.__qualname__)
|
||||
self.identity_column = self.identity_table.__getattribute__(self.identity_table,
|
||||
self.uninitialized_database_config.identity_column_name)
|
||||
self.identity_chain = relationshiplinkchain(self.master_table, self.identity_table)
|
||||
|
|
|
@ -166,15 +166,17 @@ class TelegramBot(GenericBot):
|
|||
try:
|
||||
await command.run(CommandArgs(parameters), data)
|
||||
except InvalidInputError as e:
|
||||
await data.reply(f"⚠️ {' '.join(e.args)}\n"
|
||||
await data.reply(f"⚠️ {e.message}\n"
|
||||
f"Syntax: [c]/{command.name} {command.syntax}[/c]")
|
||||
except UnsupportedError as e:
|
||||
await data.reply(f"⚠️ {e.message}")
|
||||
except CommandError as e:
|
||||
await data.reply(f"⚠️ {e.message}")
|
||||
except Exception as e:
|
||||
sentry_sdk.capture_exception(e)
|
||||
error_message = f"🦀 [b]{e.__class__.__name__}[/b] 🦀\n"
|
||||
error_message += '\n'.join(e.args)
|
||||
await data.reply(error_message)
|
||||
if __debug__:
|
||||
raise
|
||||
|
||||
async def _handle_callback_query(self, update: telegram.Update):
|
||||
query: telegram.CallbackQuery = update.callback_query
|
||||
|
|
|
@ -8,17 +8,11 @@ from .color import ColorCommand
|
|||
from .cv import CvCommand
|
||||
from .diario import DiarioCommand
|
||||
from .mp3 import Mp3Command
|
||||
from .pause import PauseCommand
|
||||
from .ping import PingCommand
|
||||
from .play import PlayCommand
|
||||
from .playmode import PlaymodeCommand
|
||||
from .queue import QueueCommand
|
||||
from .rage import RageCommand
|
||||
from .reminder import ReminderCommand
|
||||
from .ship import ShipCommand
|
||||
from .skip import SkipCommand
|
||||
from .smecds import SmecdsCommand
|
||||
from .summon import SummonCommand
|
||||
from .videochannel import VideochannelCommand
|
||||
from .dnditem import DnditemCommand
|
||||
from .dndspell import DndspellCommand
|
||||
|
@ -33,17 +27,11 @@ commands = [
|
|||
CvCommand,
|
||||
DiarioCommand,
|
||||
Mp3Command,
|
||||
PauseCommand,
|
||||
PingCommand,
|
||||
PlayCommand,
|
||||
PlaymodeCommand,
|
||||
QueueCommand,
|
||||
RageCommand,
|
||||
ReminderCommand,
|
||||
ShipCommand,
|
||||
SkipCommand,
|
||||
SmecdsCommand,
|
||||
SummonCommand,
|
||||
VideochannelCommand,
|
||||
DnditemCommand,
|
||||
DndspellCommand,
|
||||
|
|
|
@ -7,7 +7,7 @@ from ..commanddata import CommandData
|
|||
from ...network import Request, ResponseSuccess
|
||||
from ...utils import NetworkHandler, andformat
|
||||
from ...bots import DiscordBot
|
||||
from ...error import *
|
||||
from ..commanderrors import CommandError
|
||||
|
||||
|
||||
class CvNH(NetworkHandler):
|
||||
|
@ -17,13 +17,14 @@ class CvNH(NetworkHandler):
|
|||
async def discord(cls, bot: "DiscordBot", data: dict):
|
||||
# Find the matching guild
|
||||
if data["guild_name"]:
|
||||
guild: discord.Guild = bot.client.find_guild_by_name(data["guild_name"])
|
||||
guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(data["guild_name"])
|
||||
else:
|
||||
if len(bot.client.guilds) == 0:
|
||||
raise NoneFoundError("No guilds found")
|
||||
if len(bot.client.guilds) > 1:
|
||||
raise TooManyFoundError("Multiple guilds found")
|
||||
guild = list(bot.client.guilds)[0]
|
||||
guilds = bot.client.guilds
|
||||
if len(guilds) == 0:
|
||||
raise CommandError("No guilds with the specified name found.")
|
||||
if len(guilds) > 1:
|
||||
raise CommandError("Multiple guilds with the specified name found.")
|
||||
guild = list(bot.client.guilds)[0]
|
||||
# Edit the message, sorted by channel
|
||||
discord_members = list(guild.members)
|
||||
channels = {0: None}
|
||||
|
@ -119,7 +120,7 @@ class CvCommand(Command):
|
|||
|
||||
description: str = "Elenca le persone attualmente connesse alla chat vocale."
|
||||
|
||||
syntax: str = "[guildname] "
|
||||
syntax: str = "[guildname] ['all']"
|
||||
|
||||
def __init__(self, interface: CommandInterface):
|
||||
super().__init__(interface)
|
||||
|
|
|
@ -8,7 +8,7 @@ from ..commandargs import CommandArgs
|
|||
from ..commanddata import CommandData
|
||||
from ...database.tables import User, Diario, Alias
|
||||
from ...utils import asyncify
|
||||
from ...error import *
|
||||
from ..commanderrors import CommandError, InvalidInputError
|
||||
|
||||
|
||||
async def to_imgur(imgur_api_key, photosizes: typing.List[telegram.PhotoSize], caption="") -> str:
|
||||
|
@ -27,7 +27,7 @@ async def to_imgur(imgur_api_key, photosizes: typing.List[telegram.PhotoSize], c
|
|||
}) as request:
|
||||
response = await request.json()
|
||||
if not response["success"]:
|
||||
raise ExternalError("imgur returned an error in the image upload.")
|
||||
raise CommandError("Imgur returned an error in the image upload.")
|
||||
return response["data"]["link"]
|
||||
|
||||
|
||||
|
|
|
@ -4,11 +4,12 @@ import telegram
|
|||
import asyncio
|
||||
import re
|
||||
import logging
|
||||
import typing
|
||||
from ..command import Command
|
||||
from ..commandargs import CommandArgs
|
||||
from ..commanddata import CommandData
|
||||
from ...database.tables import MMEvent, MMDecision, MMResponse
|
||||
from ...error import *
|
||||
from ..commanderrors import InvalidInputError, UnsupportedError
|
||||
from ...utils import asyncify, telegram_escape, sleep_until
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
|
@ -11,8 +11,7 @@ from ..commandinterface import CommandInterface
|
|||
from ..commanddata import CommandData
|
||||
from ...utils import sleep_until, asyncify, telegram_escape, discord_escape
|
||||
from ...database.tables import Reminder
|
||||
from ...error import *
|
||||
|
||||
from ..commanderrors import InvalidInputError, UnsupportedError
|
||||
|
||||
class ReminderCommand(Command):
|
||||
name: str = "reminder"
|
||||
|
@ -76,7 +75,7 @@ class ReminderCommand(Command):
|
|||
elif self.interface.name == "discord":
|
||||
interface_data = pickle.dumps(data.message.channel.id)
|
||||
else:
|
||||
raise UnsupportedError("Interface not supported")
|
||||
raise UnsupportedError("This command does not support the current interface.")
|
||||
creator = await data.get_author()
|
||||
reminder = self.interface.alchemy.Reminder(creator=creator,
|
||||
interface_name=self.interface.name,
|
||||
|
|
|
@ -9,7 +9,7 @@ from ..commandargs import CommandArgs
|
|||
from ..commanddata import CommandData
|
||||
from ..commandinterface import CommandInterface
|
||||
from ...utils import asyncify
|
||||
from ...error import *
|
||||
from ..commanderrors import CommandError, KeyboardExpiredError
|
||||
from ...database.tables import TriviaScore
|
||||
|
||||
|
||||
|
@ -39,7 +39,7 @@ class TriviaCommand(Command):
|
|||
j = await response.json()
|
||||
# Parse the question
|
||||
if j["response_code"] != 0:
|
||||
raise ExternalError(f"OpenTDB returned {j['response_code']} response_code")
|
||||
raise CommandError(f"OpenTDB returned an error response_code ({j['response_code']}).")
|
||||
question = j["results"][0]
|
||||
text = f'❓ [b]{question["category"]} - {question["difficulty"].capitalize()}[/b]\n' \
|
||||
f'{html.unescape(question["question"])}'
|
||||
|
|
|
@ -3,7 +3,7 @@ import discord
|
|||
from ..command import Command
|
||||
from ..commandargs import CommandArgs
|
||||
from ..commanddata import CommandData
|
||||
from ...error import *
|
||||
from ..commanderrors import CommandError, UnsupportedError
|
||||
|
||||
|
||||
class VideochannelCommand(Command):
|
||||
|
@ -30,18 +30,15 @@ class VideochannelCommand(Command):
|
|||
if channel.name == channel_name:
|
||||
matching_channels.append(channel)
|
||||
if len(matching_channels) == 0:
|
||||
await data.reply("⚠️ Non esiste alcun canale vocale con il nome specificato.")
|
||||
return
|
||||
raise CommandError("Non esiste alcun canale vocale con il nome specificato.")
|
||||
elif len(matching_channels) > 1:
|
||||
await data.reply("⚠️ Esiste più di un canale vocale con il nome specificato.")
|
||||
return
|
||||
raise CommandError("Esiste più di un canale vocale con il nome specificato.")
|
||||
channel = matching_channels[0]
|
||||
else:
|
||||
author: discord.Member = message.author
|
||||
voice: typing.Optional[discord.VoiceState] = author.voice
|
||||
if voice is None:
|
||||
await data.reply("⚠️ Non sei connesso a nessun canale vocale!")
|
||||
return
|
||||
raise CommandError("Non sei connesso a nessun canale vocale.")
|
||||
channel = voice.channel
|
||||
if author.is_on_mobile():
|
||||
await data.reply(f"📹 Per entrare in modalità video, clicca qui: <https://discordapp.com/channels/{channel.guild.id}/{channel.id}>\n[b]Attenzione: la modalità video non funziona su Discord per Android e iOS![/b]")
|
||||
|
|
|
@ -8,7 +8,7 @@ from ..commandargs import CommandArgs
|
|||
from ..commanddata import CommandData
|
||||
from ...utils import NetworkHandler, asyncify
|
||||
from ...network import Request, ResponseSuccess
|
||||
from ...error import *
|
||||
from ..commanderrors import CommandError, InvalidInputError, UnsupportedError, KeyboardExpiredError
|
||||
from ...audio import YtdlDiscord
|
||||
from ...audio.playmodes import Playlist
|
||||
if typing.TYPE_CHECKING:
|
||||
|
@ -27,13 +27,14 @@ class ZawarudoNH(NetworkHandler):
|
|||
async def discord(cls, bot: "DiscordBot", data: dict):
|
||||
# Find the matching guild
|
||||
if data["guild_name"]:
|
||||
guild = bot.client.find_guild(data["guild_name"])
|
||||
guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(data["guild_name"])
|
||||
else:
|
||||
if len(bot.music_data) == 0:
|
||||
raise NoneFoundError("No voice clients active")
|
||||
if len(bot.music_data) > 1:
|
||||
raise TooManyFoundError("Multiple guilds found")
|
||||
guild = list(bot.music_data)[0]
|
||||
guilds = bot.client.guilds
|
||||
if len(guilds) == 0:
|
||||
raise CommandError("No guilds with the specified name found.")
|
||||
if len(guilds) > 1:
|
||||
raise CommandError("Multiple guilds with the specified name found.")
|
||||
guild = list(bot.client.guilds)[0]
|
||||
# Ensure the guild has a PlayMode before adding the file to it
|
||||
if not bot.music_data.get(guild):
|
||||
# TODO: change Exception
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
"""Commands that can be used in bots.
|
||||
|
||||
These probably won't suit your needs, as they are tailored for the bots of the User Games gaming community, but they
|
||||
may be useful to develop new ones."""
|
||||
|
||||
from .pause import PauseCommand
|
||||
from .play import PlayCommand
|
||||
from .playmode import PlaymodeCommand
|
||||
from .queue import QueueCommand
|
||||
from .skip import SkipCommand
|
||||
from .summon import SummonCommand
|
||||
|
||||
|
||||
commands = [
|
||||
PauseCommand,
|
||||
PlayCommand,
|
||||
PlaymodeCommand,
|
||||
QueueCommand,
|
||||
SkipCommand,
|
||||
SummonCommand,
|
||||
]
|
||||
|
||||
|
||||
__all__ = [command.__name__ for command in commands]
|
|
@ -6,7 +6,7 @@ from ..commandargs import CommandArgs
|
|||
from ..commanddata import CommandData
|
||||
from ...utils import NetworkHandler
|
||||
from ...network import Request, ResponseSuccess
|
||||
from ...error import NoneFoundError
|
||||
from ..commanderrors import CommandError
|
||||
if typing.TYPE_CHECKING:
|
||||
from ...bots import DiscordBot
|
||||
|
||||
|
@ -19,17 +19,18 @@ class PauseNH(NetworkHandler):
|
|||
async def discord(cls, bot: "DiscordBot", data: dict):
|
||||
# Find the matching guild
|
||||
if data["guild_name"]:
|
||||
guild = bot.client.find_guild_by_name(data["guild_name"])
|
||||
guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(data["guild_name"])
|
||||
else:
|
||||
if len(bot.music_data) == 0:
|
||||
raise NoneFoundError("No voice clients active")
|
||||
if len(bot.music_data) > 1:
|
||||
raise TooManyFoundError("Multiple guilds found")
|
||||
guild = list(bot.music_data)[0]
|
||||
guilds = bot.client.guilds
|
||||
if len(guilds) == 0:
|
||||
raise CommandError("No guilds with the specified name found.")
|
||||
if len(guilds) > 1:
|
||||
raise CommandError("Multiple guilds with the specified name found.")
|
||||
guild = list(bot.client.guilds)[0]
|
||||
# Set the currently playing source as ended
|
||||
voice_client: discord.VoiceClient = bot.client.find_voice_client_by_guild(guild)
|
||||
if not (voice_client.is_playing() or voice_client.is_paused()):
|
||||
raise NoneFoundError("Nothing to pause")
|
||||
raise CommandError("There is nothing to pause.")
|
||||
# Toggle pause
|
||||
resume = voice_client._player.is_paused()
|
||||
if resume:
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import typing
|
||||
import pickle
|
||||
import datetime
|
||||
import discord
|
||||
from ..command import Command
|
||||
from ..commandinterface import CommandInterface
|
||||
from ..commandargs import CommandArgs
|
||||
from ..commanddata import CommandData
|
||||
from ...utils import NetworkHandler, asyncify
|
||||
from ...network import Request, ResponseSuccess
|
||||
from ...error import *
|
||||
from ..commanderrors import CommandError
|
||||
from ...audio import YtdlDiscord
|
||||
if typing.TYPE_CHECKING:
|
||||
from ...bots import DiscordBot
|
||||
|
@ -21,13 +22,14 @@ class PlayNH(NetworkHandler):
|
|||
"""Handle a play Royalnet request. That is, add audio to a PlayMode."""
|
||||
# Find the matching guild
|
||||
if data["guild_name"]:
|
||||
guild = bot.client.find_guild(data["guild_name"])
|
||||
guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(data["guild_name"])
|
||||
else:
|
||||
if len(bot.music_data) == 0:
|
||||
raise NoneFoundError("No voice clients active")
|
||||
if len(bot.music_data) > 1:
|
||||
raise TooManyFoundError("Multiple guilds found")
|
||||
guild = list(bot.music_data)[0]
|
||||
guilds = bot.client.guilds
|
||||
if len(guilds) == 0:
|
||||
raise CommandError("No guilds with the specified name found.")
|
||||
if len(guilds) > 1:
|
||||
raise CommandError("Multiple guilds with the specified name found.")
|
||||
guild = list(bot.client.guilds)[0]
|
||||
# Ensure the guild has a PlayMode before adding the file to it
|
||||
if not bot.music_data.get(guild):
|
||||
# TODO: change Exception
|
||||
|
@ -53,7 +55,7 @@ class PlayNH(NetworkHandler):
|
|||
class PlayCommand(Command):
|
||||
name: str = "play"
|
||||
|
||||
description: str = "Aggiunge una canzone alla coda della chat vocale."
|
||||
description: str = "Aggiunge un url alla coda della chat vocale."
|
||||
|
||||
syntax = "[ [guild] ] (url)"
|
||||
|
||||
|
@ -64,7 +66,9 @@ class PlayCommand(Command):
|
|||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
guild_name, url = args.match(r"(?:\[(.+)])?\s*<?(.+)>?")
|
||||
if not (url.startswith("http://") or url.startswith("https://")):
|
||||
raise
|
||||
raise CommandError("PlayCommand only accepts URLs.\n"
|
||||
"If you want to search a song on YouTube or Soundcloud, please use YoutubeCommand"
|
||||
" or SoundcloudCommand!")
|
||||
response = await self.interface.net_request(Request("music_play", {"url": url, "guild_name": guild_name}), "discord")
|
||||
if len(response["videos"]) == 0:
|
||||
await data.reply(f"⚠️ Nessun video trovato.")
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import typing
|
||||
import pickle
|
||||
import discord
|
||||
from ..command import Command
|
||||
from ..commandinterface import CommandInterface
|
||||
from ..commandargs import CommandArgs
|
||||
from ..commanddata import CommandData
|
||||
from ...utils import NetworkHandler
|
||||
from ...network import Request, ResponseSuccess
|
||||
from ...error import *
|
||||
from ..commanderrors import CommandError
|
||||
from ...audio.playmodes import Playlist, Pool, Layers
|
||||
if typing.TYPE_CHECKING:
|
||||
from ...bots import DiscordBot
|
||||
|
@ -20,13 +21,14 @@ class PlaymodeNH(NetworkHandler):
|
|||
"""Handle a playmode Royalnet request. That is, change current PlayMode."""
|
||||
# Find the matching guild
|
||||
if data["guild_name"]:
|
||||
guild = bot.client.find_guild(data["guild_name"])
|
||||
guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(data["guild_name"])
|
||||
else:
|
||||
if len(bot.music_data) == 0:
|
||||
raise NoneFoundError("No voice clients active")
|
||||
if len(bot.music_data) > 1:
|
||||
raise TooManyFoundError("Multiple guilds found")
|
||||
guild = list(bot.music_data)[0]
|
||||
guilds = bot.client.guilds
|
||||
if len(guilds) == 0:
|
||||
raise CommandError("No guilds with the specified name found.")
|
||||
if len(guilds) > 1:
|
||||
raise CommandError("Multiple guilds with the specified name found.")
|
||||
guild = list(bot.client.guilds)[0]
|
||||
# Delete the previous PlayMode, if it exists
|
||||
if bot.music_data[guild] is not None:
|
||||
bot.music_data[guild].delete()
|
||||
|
@ -38,7 +40,7 @@ class PlaymodeNH(NetworkHandler):
|
|||
elif data["mode_name"] == "layers":
|
||||
bot.music_data[guild] = Layers()
|
||||
else:
|
||||
raise ValueError("No such PlayMode")
|
||||
raise CommandError("Unknown PlayMode specified.")
|
||||
return ResponseSuccess()
|
||||
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import typing
|
||||
import pickle
|
||||
import discord
|
||||
from ..command import Command
|
||||
from ..commandinterface import CommandInterface
|
||||
from ..commandargs import CommandArgs
|
||||
from ..commanddata import CommandData
|
||||
from ...utils import NetworkHandler, numberemojiformat
|
||||
from ...network import Request, ResponseSuccess
|
||||
from ...error import *
|
||||
from ..commanderrors import CommandError
|
||||
if typing.TYPE_CHECKING:
|
||||
from ...bots import DiscordBot
|
||||
|
||||
|
@ -18,13 +19,14 @@ class QueueNH(NetworkHandler):
|
|||
async def discord(cls, bot: "DiscordBot", data: dict):
|
||||
# Find the matching guild
|
||||
if data["guild_name"]:
|
||||
guild = bot.client.find_guild_by_name(data["guild_name"])
|
||||
guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(data["guild_name"])
|
||||
else:
|
||||
if len(bot.music_data) == 0:
|
||||
raise NoneFoundError("No voice clients active")
|
||||
if len(bot.music_data) > 1:
|
||||
raise TooManyFoundError("Multiple guilds found")
|
||||
guild = list(bot.music_data)[0]
|
||||
guilds = bot.client.guilds
|
||||
if len(guilds) == 0:
|
||||
raise CommandError("No guilds with the specified name found.")
|
||||
if len(guilds) > 1:
|
||||
raise CommandError("Multiple guilds with the specified name found.")
|
||||
guild = list(bot.client.guilds)[0]
|
||||
# Check if the guild has a PlayMode
|
||||
playmode = bot.music_data.get(guild)
|
||||
if not playmode:
|
||||
|
|
|
@ -7,7 +7,7 @@ from ..commandargs import CommandArgs
|
|||
from ..commanddata import CommandData
|
||||
from ...utils import NetworkHandler, asyncify
|
||||
from ...network import Request, ResponseSuccess
|
||||
from ...error import *
|
||||
from ..commanderrors import CommandError
|
||||
from ...audio import YtdlDiscord
|
||||
if typing.TYPE_CHECKING:
|
||||
from ...bots import DiscordBot
|
||||
|
@ -20,17 +20,18 @@ class SkipNH(NetworkHandler):
|
|||
async def discord(cls, bot: "DiscordBot", data: dict):
|
||||
# Find the matching guild
|
||||
if data["guild_name"]:
|
||||
guild = bot.client.find_guild_by_name(data["guild_name"])
|
||||
guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(data["guild_name"])
|
||||
else:
|
||||
if len(bot.music_data) == 0:
|
||||
raise NoneFoundError("No voice clients active")
|
||||
if len(bot.music_data) > 1:
|
||||
raise TooManyFoundError("Multiple guilds found")
|
||||
guild = list(bot.music_data)[0]
|
||||
guilds = bot.client.guilds
|
||||
if len(guilds) == 0:
|
||||
raise CommandError("No guilds with the specified name found.")
|
||||
if len(guilds) > 1:
|
||||
raise CommandError("Multiple guilds with the specified name found.")
|
||||
guild = list(bot.client.guilds)[0]
|
||||
# Set the currently playing source as ended
|
||||
voice_client: discord.VoiceClient = bot.client.find_voice_client_by_guild(guild)
|
||||
if not (voice_client.is_playing() or voice_client.is_paused()):
|
||||
raise NoneFoundError("Nothing to skip")
|
||||
raise CommandError("Nothing to skip")
|
||||
# noinspection PyProtectedMember
|
||||
voice_client._player.stop()
|
||||
return ResponseSuccess()
|
||||
|
|
84
royalnet/commands/royalmusic/soundcloud.py
Normal file
84
royalnet/commands/royalmusic/soundcloud.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
import typing
|
||||
import pickle
|
||||
import datetime
|
||||
import discord
|
||||
from ..command import Command
|
||||
from ..commandinterface import CommandInterface
|
||||
from ..commandargs import CommandArgs
|
||||
from ..commanddata import CommandData
|
||||
from ...utils import NetworkHandler, asyncify
|
||||
from ...network import Request, ResponseSuccess
|
||||
from ..commanderrors import CommandError
|
||||
from ...audio import YtdlDiscord
|
||||
if typing.TYPE_CHECKING:
|
||||
from ...bots import DiscordBot
|
||||
|
||||
|
||||
class SoundcloudNH(NetworkHandler):
|
||||
message_type = "music_soundcloud"
|
||||
|
||||
@classmethod
|
||||
async def discord(cls, bot: "DiscordBot", data: dict):
|
||||
"""Handle a play Royalnet request. That is, add audio to a PlayMode."""
|
||||
# Find the matching guild
|
||||
if data["guild_name"]:
|
||||
guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(data["guild_name"])
|
||||
else:
|
||||
guilds = bot.client.guilds
|
||||
if len(guilds) == 0:
|
||||
raise CommandError("No guilds with the specified name found.")
|
||||
if len(guilds) > 1:
|
||||
raise CommandError("Multiple guilds with the specified name found.")
|
||||
guild = list(bot.client.guilds)[0]
|
||||
# Ensure the guild has a PlayMode before adding the file to it
|
||||
if not bot.music_data.get(guild):
|
||||
raise KeyError("No music data available for this guild.")
|
||||
# Create url
|
||||
ytdl_args = {
|
||||
"format": "bestaudio",
|
||||
"outtmpl": f"./downloads/{datetime.datetime.now().timestamp()}_%(title)s.%(ext)s"
|
||||
}
|
||||
# Start downloading
|
||||
dfiles: typing. List[YtdlDiscord] = await asyncify(YtdlDiscord.create_from_url, f'scsearch:{data["search"]}',
|
||||
**ytdl_args)
|
||||
await bot.add_to_music_data(dfiles, guild)
|
||||
# Create response dictionary
|
||||
response = {
|
||||
"videos": [{
|
||||
"title": dfile.info.title,
|
||||
"discord_embed_pickle": str(pickle.dumps(dfile.info.to_discord_embed()))
|
||||
} for dfile in dfiles]
|
||||
}
|
||||
return ResponseSuccess(response)
|
||||
|
||||
|
||||
class SoundcloudCommand(Command):
|
||||
name: str = "soundcloud"
|
||||
|
||||
aliases = ["sc"]
|
||||
|
||||
description: str = "Cerca una canzone su Soundcloud e la aggiunge alla coda della chat vocale."
|
||||
|
||||
syntax = "[ [guild] ] (url)"
|
||||
|
||||
def __init__(self, interface: CommandInterface):
|
||||
super().__init__(interface)
|
||||
interface.register_net_handler(SoundcloudNH.message_type, SoundcloudNH)
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
guild_name, search = args.match(r"(?:\[(.+)])?\s*<?(.+)>?")
|
||||
if search.startswith("http://") or search.startswith("https://"):
|
||||
raise CommandError("YoutubeCommand only accepts search queries, and you've sent an URL.\n"
|
||||
"If you want to add a song from an url, please use PlayCommand!")
|
||||
response = await self.interface.net_request(Request("music_soundcloud", {"search": search,
|
||||
"guild_name": guild_name}),
|
||||
"discord")
|
||||
if len(response["videos"]) == 0:
|
||||
await data.reply(f"⚠️ Nessun video trovato.")
|
||||
for video in response["videos"]:
|
||||
if self.interface.name == "discord":
|
||||
# This is one of the unsafest things ever
|
||||
embed = pickle.loads(eval(video["discord_embed_pickle"]))
|
||||
await data.message.channel.send(content="▶️ Aggiunto alla coda:", embed=embed)
|
||||
else:
|
||||
await data.reply(f"▶️ Aggiunto alla coda: [i]{video['title']}[/i]")
|
|
@ -6,7 +6,7 @@ from ..commandargs import CommandArgs
|
|||
from ..commanddata import CommandData
|
||||
from ...utils import NetworkHandler
|
||||
from ...network import Request, ResponseSuccess
|
||||
from ...error import NoneFoundError
|
||||
from ..commanderrors import CommandError
|
||||
if typing.TYPE_CHECKING:
|
||||
from ...bots import DiscordBot
|
||||
|
||||
|
@ -18,9 +18,10 @@ class SummonNH(NetworkHandler):
|
|||
async def discord(cls, bot: "DiscordBot", data: dict):
|
||||
"""Handle a summon Royalnet request.
|
||||
That is, join a voice channel, or move to a different one if that is not possible."""
|
||||
channel = bot.client.find_channel_by_name(data["channel_name"])
|
||||
channels = bot.client.find_channel_by_name(data["channel_name"])
|
||||
channel = channels[0]
|
||||
if not isinstance(channel, discord.VoiceChannel):
|
||||
raise NoneFoundError("Channel is not a voice channel")
|
||||
raise CommandError("Channel is not a voice channel")
|
||||
bot.loop.create_task(bot.client.vc_connect_or_move(channel))
|
||||
return ResponseSuccess()
|
||||
|
||||
|
|
83
royalnet/commands/royalmusic/youtube.py
Normal file
83
royalnet/commands/royalmusic/youtube.py
Normal file
|
@ -0,0 +1,83 @@
|
|||
import typing
|
||||
import pickle
|
||||
import datetime
|
||||
import discord
|
||||
from ..command import Command
|
||||
from ..commandinterface import CommandInterface
|
||||
from ..commandargs import CommandArgs
|
||||
from ..commanddata import CommandData
|
||||
from ...utils import NetworkHandler, asyncify
|
||||
from ...network import Request, ResponseSuccess
|
||||
from ..commanderrors import CommandError
|
||||
from ...audio import YtdlDiscord
|
||||
if typing.TYPE_CHECKING:
|
||||
from ...bots import DiscordBot
|
||||
|
||||
|
||||
class YoutubeNH(NetworkHandler):
|
||||
message_type = "music_youtube"
|
||||
|
||||
@classmethod
|
||||
async def discord(cls, bot: "DiscordBot", data: dict):
|
||||
"""Handle a play Royalnet request. That is, add audio to a PlayMode."""
|
||||
# Find the matching guild
|
||||
if data["guild_name"]:
|
||||
guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(data["guild_name"])
|
||||
else:
|
||||
guilds = bot.client.guilds
|
||||
if len(guilds) == 0:
|
||||
raise CommandError("No guilds with the specified name found.")
|
||||
if len(guilds) > 1:
|
||||
raise CommandError("Multiple guilds with the specified name found.")
|
||||
guild = list(bot.client.guilds)[0]
|
||||
# Ensure the guild has a PlayMode before adding the file to it
|
||||
if not bot.music_data.get(guild):
|
||||
raise KeyError("No music data available for this guild.")
|
||||
# Create url
|
||||
ytdl_args = {
|
||||
"format": "bestaudio",
|
||||
"outtmpl": f"./downloads/{datetime.datetime.now().timestamp()}_%(title)s.%(ext)s"
|
||||
}
|
||||
# Start downloading
|
||||
dfiles: typing. List[YtdlDiscord] = await asyncify(YtdlDiscord.create_from_url, f'ytsearch:{data["search"]}', **ytdl_args)
|
||||
await bot.add_to_music_data(dfiles, guild)
|
||||
# Create response dictionary
|
||||
response = {
|
||||
"videos": [{
|
||||
"title": dfile.info.title,
|
||||
"discord_embed_pickle": str(pickle.dumps(dfile.info.to_discord_embed()))
|
||||
} for dfile in dfiles]
|
||||
}
|
||||
return ResponseSuccess(response)
|
||||
|
||||
|
||||
class YoutubeCommand(Command):
|
||||
name: str = "youtube"
|
||||
|
||||
aliases = ["yt"]
|
||||
|
||||
description: str = "Cerca un video su YouTube e lo aggiunge alla coda della chat vocale."
|
||||
|
||||
syntax = "[ [guild] ] (url)"
|
||||
|
||||
def __init__(self, interface: CommandInterface):
|
||||
super().__init__(interface)
|
||||
interface.register_net_handler(YoutubeNH.message_type, YoutubeNH)
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
guild_name, search = args.match(r"(?:\[(.+)])?\s*<?(.+)>?")
|
||||
if search.startswith("http://") or search.startswith("https://"):
|
||||
raise CommandError("YoutubeCommand only accepts search queries, and you've sent an URL.\n"
|
||||
"If you want to add a song from an url, please use PlayCommand!")
|
||||
response = await self.interface.net_request(Request("music_youtube", {"search": search,
|
||||
"guild_name": guild_name}),
|
||||
"discord")
|
||||
if len(response["videos"]) == 0:
|
||||
await data.reply(f"⚠️ Nessun video trovato.")
|
||||
for video in response["videos"]:
|
||||
if self.interface.name == "discord":
|
||||
# This is one of the unsafest things ever
|
||||
embed = pickle.loads(eval(video["discord_embed_pickle"]))
|
||||
await data.message.channel.send(content="▶️ Aggiunto alla coda:", embed=embed)
|
||||
else:
|
||||
await data.reply(f"▶️ Aggiunto alla coda: [i]{video['title']}[/i]")
|
|
@ -4,8 +4,6 @@ from sqlalchemy.ext.declarative import declarative_base
|
|||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from contextlib import contextmanager, asynccontextmanager
|
||||
from ..utils import asyncify
|
||||
# noinspection PyUnresolvedReferences
|
||||
from ..error import InvalidConfigError
|
||||
|
||||
|
||||
class Alchemy:
|
||||
|
|
Loading…
Reference in a new issue