diff --git a/docs/doctrees/apireference.doctree b/docs/doctrees/apireference.doctree index f14ba02f..46fb5e7e 100644 Binary files a/docs/doctrees/apireference.doctree and b/docs/doctrees/apireference.doctree differ diff --git a/docs/doctrees/creatingacommand.doctree b/docs/doctrees/creatingacommand.doctree index 5798cb98..136be5ed 100644 Binary files a/docs/doctrees/creatingacommand.doctree and b/docs/doctrees/creatingacommand.doctree differ diff --git a/docs/doctrees/environment.pickle b/docs/doctrees/environment.pickle index e09f62b1..11a44a23 100644 Binary files a/docs/doctrees/environment.pickle and b/docs/doctrees/environment.pickle differ diff --git a/docs/doctrees/index.doctree b/docs/doctrees/index.doctree index 54662b5a..87e2c3a7 100644 Binary files a/docs/doctrees/index.doctree and b/docs/doctrees/index.doctree differ diff --git a/docs/doctrees/runningroyalnet.doctree b/docs/doctrees/runningroyalnet.doctree index b5402576..c2722e31 100644 Binary files a/docs/doctrees/runningroyalnet.doctree and b/docs/doctrees/runningroyalnet.doctree differ diff --git a/docs/html/_sources/creatingacommand.rst.txt b/docs/html/_sources/creatingacommand.rst.txt index 248a6068..037557cd 100644 --- a/docs/html/_sources/creatingacommand.rst.txt +++ b/docs/html/_sources/creatingacommand.rst.txt @@ -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 `_. 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 --------------------------------------------- diff --git a/docs/html/_sources/runningroyalnet.rst.txt b/docs/html/_sources/runningroyalnet.rst.txt index 91376c26..519fe1a0 100644 --- a/docs/html/_sources/runningroyalnet.rst.txt +++ b/docs/html/_sources/runningroyalnet.rst.txt @@ -3,4 +3,60 @@ Running Royalnet ==================================== -This documentation page hasn't been written yet, please refer to the README until then. \ No newline at end of file +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 +`_. + +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 `_. :: + + 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 `_. :: + + Discord Bot API token []: AAAAAAAAAAAAAAAAAAAAAAAA.AAAAAA.AAAAAAAAAAAAAAAAAAAAAAAAAAA + +Now the configurator will ask you for a Imgur API token. +`Register an application `_ 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 \ No newline at end of file diff --git a/docs/html/apireference.html b/docs/html/apireference.html index 07133d90..fa397e6a 100644 --- a/docs/html/apireference.html +++ b/docs/html/apireference.html @@ -286,11 +286,6 @@ convert_to_pcm() → None
-
-
-classmethod create_and_ready_from_url(url, **ytdl_args) → List[royalnet.audio.ytdldiscord.YtdlDiscord]
-
-
classmethod create_from_url(url, **ytdl_args) → List[royalnet.audio.ytdldiscord.YtdlDiscord]
@@ -573,7 +568,7 @@ find the chain that links the
-run_blocking()
+run_blocking(verbose=False)
@@ -657,6 +652,13 @@ find the chain that links the class royalnet.commands.Command(interface: royalnet.commands.commandinterface.CommandInterface)
+
+aliases = NotImplemented
+

A list of possible aliases for a command. +To have /e as alias for /example, one should set aliases to ["e"].

+
+ +
description = NotImplemented

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

@@ -683,7 +685,7 @@ To have /example
syntax = ''
-

The syntax of the command, to be displayed when a royalnet.error.InvalidInputError is raised, +

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

@@ -694,12 +696,12 @@ in the format (requ class royalnet.commands.CommandData
-async delete_invoking(error_if_unavailable=False)
+async delete_invoking(error_if_unavailable=False) → None

Delete the invoking message, if supported by the interface.

The invoking message is the message send by the user that contains the command.

Parameters
-

error_if_unavailable – if True, raise NotImplementedError() if the message cannot been deleted

+

error_if_unavailable – if True, raise an exception if the message cannot been deleted.

@@ -711,10 +713,7 @@ in the format (requ That probably means, the database row identifying the user.

Parameters
-

error_if_none – Raise a royalnet.error.UnregisteredError if this is True and the call has no author.

-
-
Raises
-

royalnet.error.UnregisteredError

+

error_if_none – Raise an exception if this is True and the call has no author.

@@ -749,7 +748,7 @@ That probably means, the database row identifying the user.

Arguments can be accessed with an array notation, such as args[0].

Raises
-

royalnet.error.InvalidInputError – if the requested argument does not exist.

+

royalnet.error.InvalidInputError – if the requested argument does not exist.

@@ -760,10 +759,10 @@ That probably means, the database row identifying the user.

Get the arguments as a space-joined string.

Parameters
-

require_at_least – the minimum amount of arguments required, will raise royalnet.error.InvalidInputError if the requirement is not fullfilled.

+

require_at_least – the minimum amount of arguments required, will raise royalnet.error.InvalidInputError if the requirement is not fullfilled.

Raises
-

royalnet.error.InvalidInputError – if there are less than require_at_least arguments.

+

royalnet.error.InvalidInputError – if there are less than require_at_least arguments.

Returns

The space-joined string.

@@ -780,7 +779,7 @@ That probably means, the database row identifying the user.

pattern – The regex pattern to be passed to re.match().

Raises
-

royalnet.error.InvalidInputError – if the pattern doesn’t match.

+

royalnet.error.InvalidInputError – if the pattern doesn’t match.

Returns

The matched groups, as returned by re.Match.groups().

@@ -807,6 +806,33 @@ That probably means, the database row identifying the user.

+
+
+exception royalnet.commands.CommandError(message='')
+

Something went wrong during the execution of this command.

+

Display an error message to the user, explaining what went wrong.

+
+ +
+
+exception royalnet.commands.InvalidInputError(message='')
+

The command has received invalid input and cannot complete.

+

Display an error message to the user, along with the correct syntax for the command.

+
+ +
+
+exception royalnet.commands.UnsupportedError(message='')
+

A requested feature is not available on this interface.

+

Display an error message to the user, telling them to use another interface.

+
+ +
+
+exception royalnet.commands.KeyboardExpiredError(message='')
+

A special type of exception that can be raised in keyboard handlers to mark a specific keyboard as expired.

+
+

Database

@@ -896,7 +922,7 @@ That probably means, the database row identifying the user.

-async run(loops: numbers.Real = inf)
+async run()

Blockingly run the Link.

@@ -1031,19 +1057,14 @@ Contains info about the source and the destination.

Executed every time a package is received and must be routed somewhere.

-
-
-async run()
-
-
-run_blocking()
+run_blocking(verbose=False)
-async serve()
+serve()
@@ -1347,48 +1368,6 @@ Use with caution?

Error

-
-
-exception royalnet.error.CurrentlyDisabledError
-

This feature is temporarely disabled and is not available right now.

-
- -
-
-exception royalnet.error.ExternalError
-

Something went wrong in a non-Royalnet component and the command execution cannot be completed.

-
- -
-
-exception royalnet.error.FileTooBigError
-

The file to be downloaded would be too big to store; therefore, it has been skipped.

-
- -
-
-exception royalnet.error.InvalidConfigError
-

The bot has not been configured correctly, therefore the command can not function.

-
- -
-
-exception royalnet.error.InvalidInputError
-

The command has received invalid input and cannot complete.

-
- -
-
-exception royalnet.error.KeyboardExpiredError
-

A special type of exception that can be raised in keyboard handlers to mark a specific keyboard as expired.

-
- -
-
-exception royalnet.error.NoneFoundError
-

The element that was being looked for was not found.

-
-
exception royalnet.error.RoyalnetRequestError(error: ResponseError)
@@ -1407,24 +1386,6 @@ Use with caution?

The royalnet.network.Response that was received is invalid.

-
-
-exception royalnet.error.TooManyFoundError
-

Multiple elements matching the request were found, and only one was expected.

-
- -
-
-exception royalnet.error.UnregisteredError
-

The command required a registered user, and the user was not registered.

-
- -
-
-exception royalnet.error.UnsupportedError
-

The command is not supported for the specified interface.

-
-
diff --git a/docs/html/creatingacommand.html b/docs/html/creatingacommand.html index f50124ad..3c2157dd 100644 --- a/docs/html/creatingacommand.html +++ b/docs/html/creatingacommand.html @@ -92,6 +92,7 @@
  • Regular expressions
  • +
  • Raising errors
  • Running code at the initialization of the bot
  • Coroutines and slow operations
  • Delete the invoking message
  • @@ -256,7 +257,7 @@ ones.

    You can consider arguments as if they were separated by spaces.

    You can then access command arguments directly by number as if the args object was a list of str.

    If you request an argument with a certain number, but the argument does not exist, an -royalnet.error.InvalidInputError is raised, making the arguments accessed in this way required.

    +royalnet.error.InvalidInputError is raised, making the arguments accessed in this way required.

    args[0]
     # "carbonara"
     
    @@ -295,7 +296,7 @@ will return # "carbonara al-dente"
     
    -

    You can specify a minimum number of arguments too, so that an royalnet.error.InvalidInputError will be +

    You can specify a minimum number of arguments too, so that an InvalidInputError will be raised if not enough arguments are present:

    args.joined(require_at_least=3)
     # InvalidInputError() is raised
    @@ -306,7 +307,7 @@ raised if not enough arguments are present:

    Regular expressions

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

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

    +which returns a tuple of the matched groups and raises an InvalidInputError if there is no match.

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

    args.match(r"(carb\w+)")
     # ("carbonara",)
    @@ -323,6 +324,25 @@ which returns a tuple of the matched groups and raises an 
    +

    Raising errors

    +

    If you want to display an error message to the user, you can raise a 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 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 +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

    You can run code while the bot is starting by overriding the Command.__init__() function.

    diff --git a/docs/html/genindex.html b/docs/html/genindex.html index 835626ca..8574095f 100644 --- a/docs/html/genindex.html +++ b/docs/html/genindex.html @@ -155,7 +155,6 @@ | B | C | D - | E | F | G | H @@ -260,10 +259,12 @@
  • advance_music_data() (royalnet.bots.DiscordBot method)
  • Alchemy (class in royalnet.database) +
  • +
  • alchemy (royalnet.commands.CommandInterface attribute)
  • -
  • CurrentlyDisabledError -
  • @@ -354,22 +351,12 @@ -

    E

    - - -
    -

    F

    @@ -504,16 +489,14 @@
  • NetworkConfig (class in royalnet.network)
  • NetworkError -
  • -
  • NetworkHandler (class in royalnet.utils)
  • run_blocking() (royalnet.bots.GenericBot method) @@ -708,8 +689,6 @@
  • to_json_bytes() (royalnet.network.Package method)
  • to_json_string() (royalnet.network.Package method) -
  • -
  • TooManyFoundError
  • @@ -720,12 +699,10 @@
  • unregister_keyboard_key() (royalnet.commands.CommandInterface method)
  • unregister_net_handler() (royalnet.commands.CommandInterface method) -
  • -
  • UnregisteredError
    • -
    • UnsupportedError +
    • UnsupportedError
    • update_activity_with_source_title() (royalnet.bots.DiscordBot method)
    • diff --git a/docs/html/index.html b/docs/html/index.html index 3a5b897b..b5c0ea49 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -153,7 +153,11 @@

      Welcome to the documentation of Royalnet!

        -
      • Running Royalnet
      • +
      • Running Royalnet +
      • Royalnet Commands
        • Creating a new Command
        • Command arguments
        • +
        • Raising errors
        • Running code at the initialization of the bot
        • Coroutines and slow operations
        • Delete the invoking message
        • diff --git a/docs/html/objects.inv b/docs/html/objects.inv index a7819aa5..f9a3aae8 100644 Binary files a/docs/html/objects.inv and b/docs/html/objects.inv differ diff --git a/docs/html/runningroyalnet.html b/docs/html/runningroyalnet.html index f822c635..35718563 100644 --- a/docs/html/runningroyalnet.html +++ b/docs/html/runningroyalnet.html @@ -82,7 +82,11 @@ @@ -151,7 +155,56 @@

          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.

          +

          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 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.

          +
          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.

          +
          Discord Bot API token []: AAAAAAAAAAAAAAAAAAAAAAAA.AAAAAA.AAAAAAAAAAAAAAAAAAAAAAAAAAA
          +
          +
          +

          Now the configurator will ask you for a Imgur API token. +Register an application 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

          +
          diff --git a/docs/html/searchindex.js b/docs/html/searchindex.js index f0039614..64d68fae 100644 --- a/docs/html/searchindex.js +++ b/docs/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["apireference","creatingacommand","index","runningroyalnet"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["apireference.rst","creatingacommand.rst","index.rst","runningroyalnet.rst"],objects:{"royalnet.audio":{FileAudioSource:[0,1,1,""],YtdlDiscord:[0,1,1,""],YtdlFile:[0,1,1,""],YtdlInfo:[0,1,1,""],YtdlMp3:[0,1,1,""]},"royalnet.audio.FileAudioSource":{is_opus:[0,2,1,""],read:[0,2,1,""]},"royalnet.audio.YtdlDiscord":{"delete":[0,2,1,""],convert_to_pcm:[0,2,1,""],create_and_ready_from_url:[0,2,1,""],create_from_url:[0,2,1,""],info:[0,2,1,""],pcm_available:[0,2,1,""],ready_up:[0,2,1,""],spawn_audiosource:[0,2,1,""]},"royalnet.audio.YtdlFile":{"delete":[0,2,1,""],_default_ytdl_args:[0,3,1,""],download_file:[0,2,1,""],download_from_url:[0,2,1,""],has_info:[0,2,1,""],is_downloaded:[0,2,1,""],open:[0,2,1,""],update_info:[0,2,1,""]},"royalnet.audio.YtdlInfo":{__init__:[0,2,1,""],_default_ytdl_args:[0,3,1,""],retrieve_for_url:[0,2,1,""],to_discord_embed:[0,2,1,""]},"royalnet.audio.YtdlMp3":{"delete":[0,2,1,""],convert_to_mp3:[0,2,1,""],create_and_ready_from_url:[0,2,1,""],create_from_url:[0,2,1,""],info:[0,2,1,""],pcm_available:[0,2,1,""],ready_up:[0,2,1,""]},"royalnet.bots":{DiscordBot:[0,1,1,""],GenericBot:[0,1,1,""],TelegramBot:[0,1,1,""]},"royalnet.bots.DiscordBot":{_bot_factory:[0,2,1,""],_data_factory:[0,2,1,""],_init_client:[0,2,1,""],_init_voice:[0,2,1,""],_initialize:[0,2,1,""],_interface_factory:[0,2,1,""],add_to_music_data:[0,2,1,""],advance_music_data:[0,2,1,""],interface_name:[0,3,1,""],run:[0,2,1,""],update_activity_with_source_title:[0,2,1,""]},"royalnet.bots.GenericBot":{_data_factory:[0,2,1,""],_init_commands:[0,2,1,""],_init_database:[0,2,1,""],_init_loop:[0,2,1,""],_init_network:[0,2,1,""],_init_sentry:[0,2,1,""],_initialize:[0,2,1,""],_interface_factory:[0,2,1,""],_network_handler:[0,2,1,""],get_secret:[0,2,1,""],interface_name:[0,3,1,""],run:[0,2,1,""],run_blocking:[0,2,1,""],set_secret:[0,2,1,""]},"royalnet.bots.TelegramBot":{_data_factory:[0,2,1,""],_handle_callback_query:[0,2,1,""],_handle_message:[0,2,1,""],_handle_update:[0,2,1,""],_init_client:[0,2,1,""],_initialize:[0,2,1,""],_interface_factory:[0,2,1,""],interface_name:[0,3,1,""],run:[0,2,1,""],safe_api_call:[0,2,1,""]},"royalnet.commands":{Command:[0,1,1,""],CommandArgs:[0,1,1,""],CommandData:[0,1,1,""],CommandInterface:[0,1,1,""]},"royalnet.commands.Command":{description:[0,3,1,""],name:[0,3,1,""],require_alchemy_tables:[0,3,1,""],run:[0,2,1,""],syntax:[0,3,1,""]},"royalnet.commands.CommandArgs":{__getitem__:[0,2,1,""],joined:[0,2,1,""],match:[0,2,1,""],optional:[0,2,1,""]},"royalnet.commands.CommandData":{delete_invoking:[0,2,1,""],get_author:[0,2,1,""],keyboard:[0,2,1,""],reply:[0,2,1,""]},"royalnet.commands.CommandInterface":{alchemy:[0,3,1,""],bot:[0,3,1,""],loop:[0,3,1,""],name:[0,3,1,""],net_request:[0,2,1,""],prefix:[0,3,1,""],register_keyboard_key:[0,2,1,""],register_net_handler:[0,2,1,""],unregister_keyboard_key:[0,2,1,""],unregister_net_handler:[0,2,1,""]},"royalnet.database":{Alchemy:[0,1,1,""],DatabaseConfig:[0,1,1,""],relationshiplinkchain:[0,4,1,""]},"royalnet.database.Alchemy":{__init__:[0,2,1,""],_create_tables:[0,2,1,""],session_acm:[0,2,1,""],session_cm:[0,2,1,""]},"royalnet.error":{CurrentlyDisabledError:[0,5,1,""],ExternalError:[0,5,1,""],FileTooBigError:[0,5,1,""],InvalidConfigError:[0,5,1,""],InvalidInputError:[0,5,1,""],KeyboardExpiredError:[0,5,1,""],NoneFoundError:[0,5,1,""],RoyalnetRequestError:[0,5,1,""],RoyalnetResponseError:[0,5,1,""],TooManyFoundError:[0,5,1,""],UnregisteredError:[0,5,1,""],UnsupportedError:[0,5,1,""]},"royalnet.error.RoyalnetRequestError":{args:[0,2,1,""]},"royalnet.network":{ConnectionClosedError:[0,5,1,""],NetworkConfig:[0,1,1,""],NetworkError:[0,5,1,""],NetworkLink:[0,1,1,""],NetworkServer:[0,1,1,""],NotConnectedError:[0,5,1,""],NotIdentifiedError:[0,5,1,""],Package:[0,1,1,""],Request:[0,1,1,""],Response:[0,1,1,""],ResponseError:[0,1,1,""],ResponseSuccess:[0,1,1,""]},"royalnet.network.NetworkLink":{connect:[0,2,1,""],identify:[0,2,1,""],receive:[0,2,1,""],request:[0,2,1,""],run:[0,2,1,""],send:[0,2,1,""]},"royalnet.network.NetworkServer":{find_client:[0,2,1,""],find_destination:[0,2,1,""],listener:[0,2,1,""],route_package:[0,2,1,""],run:[0,2,1,""],run_blocking:[0,2,1,""],serve:[0,2,1,""]},"royalnet.network.Package":{__init__:[0,2,1,""],from_dict:[0,2,1,""],from_json_bytes:[0,2,1,""],from_json_string:[0,2,1,""],reply:[0,2,1,""],to_dict:[0,2,1,""],to_json_bytes:[0,2,1,""],to_json_string:[0,2,1,""]},"royalnet.network.Request":{from_dict:[0,2,1,""],to_dict:[0,2,1,""]},"royalnet.network.Response":{from_dict:[0,2,1,""],raise_on_error:[0,2,1,""],to_dict:[0,2,1,""]},"royalnet.network.ResponseError":{raise_on_error:[0,2,1,""]},"royalnet.network.ResponseSuccess":{raise_on_error:[0,2,1,""]},"royalnet.utils":{NetworkHandler:[0,1,1,""],andformat:[0,4,1,""],asyncify:[0,4,1,""],cdj:[0,4,1,""],discord_escape:[0,4,1,""],fileformat:[0,4,1,""],numberemojiformat:[0,4,1,""],ordinalformat:[0,4,1,""],parse_5etools_entry:[0,4,1,""],plusformat:[0,4,1,""],safeformat:[0,4,1,""],sleep_until:[0,4,1,""],splitstring:[0,4,1,""],telegram_escape:[0,4,1,""],ytdldateformat:[0,4,1,""]},"royalnet.utils.NetworkHandler":{message_type:[0,3,1,""]},"royalnet.web":{Royalprint:[0,1,1,""],create_app:[0,4,1,""]},royalnet:{audio:[0,0,0,"-"],bots:[0,0,0,"-"],commands:[0,0,0,"-"],database:[0,0,0,"-"],error:[0,0,0,"-"],network:[0,0,0,"-"],utils:[0,0,0,"-"],web:[0,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","attribute","Python attribute"],"4":["py","function","Python function"],"5":["py","exception","Python exception"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:attribute","4":"py:function","5":"py:exception"},terms:{"20m":0,"byte":0,"class":[0,1],"default":[0,1],"final":0,"function":[0,1],"import":1,"int":0,"new":[0,2],"null":0,"return":[0,1],"short":1,"static":[0,1],"super":1,"true":[0,1],"try":[0,1],"while":[0,1],Adding:2,And:1,For:1,Not:1,That:0,The:[0,1],Then:[0,1],These:[0,1],Use:[0,1],Using:2,__default__:0,__dict__:0,__doc__:0,__getitem__:0,__init__:[0,1],__module__:0,__slots__:0,__weakref__:0,_bot_factori:0,_create_t:0,_data_factori:0,_default_ytdl_arg:0,_handle_callback_queri:0,_handle_messag:0,_handle_upd:0,_init_cli:0,_init_command:0,_init_databas:0,_init_loop:0,_init_network:0,_init_sentri:0,_init_voic:0,_initi:0,_interface_factori:0,_network_handl:0,abl:0,about:[0,1],abstracteventloop:0,access:[0,2],accur:0,add:[0,1],add_to_music_data:0,added:[0,1],adding:[0,1],addit:0,addition:1,address:0,advance_music_data:0,after:1,akin:0,alchemi:[0,2],all:[0,1],allow:[0,1],alreadi:1,also:0,altern:0,alwai:[0,1],amount:0,andformat:0,ani:[0,1],anoth:0,anymor:0,anystr:0,api:2,app:0,append:1,applic:0,arg:[0,1],argument:[0,2],around:0,arrai:0,ascend:1,async:[0,1],asyncifi:[0,1],asyncio:[0,1],asyncron:0,attempt:0,attribut:[0,1],audio:2,audiosourc:0,author:0,autocomplet:0,automat:0,avail:[0,1],avoid:1,await:1,banana:1,base:0,becaus:1,been:[0,3],befor:1,begin:1,being:[0,1],between:[0,1],big:0,block:0,blockingli:0,blueprint:0,bool:0,bot:2,both:0,bracket:1,brief:1,bufferediobas:0,bug:1,call:[0,1],callabl:0,callback:0,can:[0,1],cancel:0,cannot:0,carb:1,carbonara:1,caus:1,caution:0,cdj:0,certain:1,chain:0,chang:[0,1],change_pres:0,channel:0,charact:0,chat:[0,1],check:0,class_:0,classmethod:0,client:0,close:0,clue:0,code:[0,2],column:1,command:2,commandarg:[0,1],commanddata:[0,1],commandinterfac:0,commun:0,comparis:1,complet:0,complex:1,compon:0,comun:2,config:0,config_obj:0,configur:0,connect:[0,1],connectedcli:0,connectionclosederror:0,consid:1,consum:0,contain:[0,1],context:0,convers:0,convert:0,convert_to_mp3:0,convert_to_pcm:0,core:0,coroutin:[0,2],correctli:0,correspond:0,could:1,creat:[0,2],create_and_ready_from_url:0,create_app:0,create_from_url:0,current:0,currentlydisablederror:0,custom:0,dai:0,data:[0,1],databas:2,database_config:0,database_uri:0,databaseconfig:0,date:0,datetim:0,db_path:0,declar:0,def:1,delet:[0,2],delete_invok:[0,1],dent:1,desc:1,descend:1,describ:0,descript:[0,1],destin:0,destination_conv_id:0,detail:0,dfile:0,dict:0,dictionari:0,differ:1,direct:2,directli:1,disabl:0,discord:0,discord_escap:0,discordbot:0,discordcli:0,displai:0,doc:0,docstr:0,document:[1,2,3],doe:[0,1],doesn:0,don:[0,1],done:1,download:0,download_1_terabyte_of_spaghetti:1,download_fil:0,download_from_url:0,dynam:0,eas:0,edit:0,effect:1,either:0,element:[0,1],els:1,emb:0,emoji:1,empti:0,encod:0,end:0,ending_class:0,engin:[0,1],enough:1,ensur:0,entri:0,envvar:0,epoch:0,error:[1,2],error_data:0,error_if_non:0,error_if_unavail:[0,1],escap:0,establish:1,even:0,event:0,everi:[0,1],everyth:0,exact:1,exampl:[0,1],except:[0,1],execut:[0,1],exist:[0,1],expect:[0,1],expir:0,express:2,ext:0,extern:1,externalerror:0,extra_info:0,extract:0,extract_info:0,fals:0,featur:0,fetch:[0,2],field:1,file:[0,1],fileaudiosourc:0,fileformat:0,filenam:0,filetoobigerror:0,filter:2,find:0,find_client:0,find_destin:0,finish:1,first:1,flag:0,flask:0,follow:0,format:0,found:[0,1],from:[0,1],from_dict:0,from_json_byt:0,from_json_str:0,from_object:0,full:2,fullfil:0,gener:0,genericbot:0,get:[0,1],get_author:0,get_secret:0,github:2,greater:[0,1],group:[0,1],guild:0,handl:0,handler:0,has:[0,1],has_info:0,hasn:3,have:[0,1],here:1,how:0,html:0,http:0,identifi:0,identity_column_nam:0,identity_t:0,ifi:0,ignor:[0,1],ignoreerror:0,imag:1,import_nam:0,incom:0,incomplet:0,index:[0,2],inf:0,info:0,inform:0,inherit:[0,1],initi:[0,2],input:0,insid:1,instanc:[0,1],instead:[0,1],interact:1,interfac:[0,1],interface_nam:0,invalid:0,invalidconfigerror:0,invalidinputerror:[0,1],invok:[0,2],is_download:0,is_opu:0,isn:[0,1],itali:1,item:0,join:[0,1],json:0,jsonabl:0,just:[0,1],keep:1,kei:0,key_nam:0,keyboard:0,keyboardexpirederror:0,keyword:1,kind:1,kwarg:0,last:0,less:0,like:[0,1],link:0,link_typ:0,list:[0,1],listen:0,login:0,look:[0,1],loop:0,made:0,mai:1,main:0,mainli:0,maintain:0,make:[0,1],manag:0,mark:0,markup:0,master_secret:0,master_t:0,master_uri:0,match:[0,1],max:0,mean:[0,1],meantim:1,member:1,memori:0,mention:1,messag:[0,2],message_typ:0,metadata:1,method:[0,1],middl:0,might:0,minimum:[0,1],miscellan:0,miss:0,month:0,more:[0,2],multipl:[0,1],music_data:0,must:0,name:[0,1],need:[0,1],net_request:0,network:2,network_config:0,network_handl:0,networkconfig:0,networkerror:0,networkhandl:0,networklink:0,networkserv:0,next:[0,1],nid:0,no_warn:0,nobodi:0,node:0,non:0,none:[0,1],nonefounderror:0,noplaylist:0,normal:1,notat:0,notconnectederror:0,noth:[0,1],notic:1,notidentifiederror:0,notimpl:0,notimplementederror:0,now:[0,1],number:[0,1],numberemojiformat:0,object:[0,1],offset:0,onc:[0,1],one:[0,1],one_or_non:1,ones:[0,1],onli:[0,1],open:0,oper:2,option:[0,2],optional_arg:0,opu:0,order:2,order_bi:1,ordinalformat:0,org:0,orm:1,other:[0,1],otherwis:[0,1],outdat:0,output:0,outtmpl:0,overrid:1,packag:0,packet:0,page:[0,3],paramet:[0,1],parenthes:1,parse_5etools_entri:0,part:1,pass:[0,1],password:0,pasta:1,path:0,pattern:[0,1],pcm:0,pcm_avail:0,pickl:0,ping:1,pingcommand:1,plai:[0,1],pleas:3,plusformat:0,pong:1,port:0,possibl:[0,1],postgresql:1,prefix:0,prepar:0,prepend:0,presenc:0,present:1,press:0,previou:1,previous:1,probabl:0,properli:1,properti:0,python:1,queri:2,quiet:0,rais:[0,1],raise_on_error:0,read:[0,1],readi:1,readm:3,ready_up:0,real:0,realli:0,receiv:[0,1],reciev:0,recogn:1,recreat:0,refer:[1,2,3],regex:0,regist:0,register_keyboard_kei:0,register_net_handl:0,regular:2,relat:0,relationshiplinkchain:0,rememb:1,remov:0,replac:0,repli:[0,1],request:[0,1],request_dict:0,request_handl:0,requested_pasta:1,requestedpasta:1,requir:[0,1],require_alchemy_t:[0,1],require_at_least:[0,1],required_arg:0,required_secret:0,required_t:0,requrire_alchemy_t:1,respect:1,respons:0,responseerror:0,responsesuccess:0,rest:1,restart:1,result:[0,2],retriev:0,retrieve_for_url:0,right:0,right_now:1,role:1,root_path:0,round:1,rout:0,route_packag:0,row:[0,1],royal:1,royalcod:0,royalnet:0,royalnetlink:0,royalnetrequesterror:0,royalnetresponseerror:0,royalprint:0,royalt:1,run:[0,2],run_block:0,safe_api_cal:0,safeformat:0,same:1,script:1,second:0,secret:0,secret_kei:0,secrets_nam:0,section:1,select:0,self:[0,1],send:[0,1],sent:[0,1],sentry_dsn:0,separ:[0,1],sequenc:0,serv:0,server:0,session:1,session_acm:0,session_cm:0,set:[0,1],set_secret:0,share:1,should:[0,1],side:1,signal:0,simpl:1,singl:[0,1],skip:0,sleep:1,sleep_until:0,slow:2,small:[0,1],some:1,someth:0,somewher:0,song:0,soon:1,sort:1,sourc:0,source_conv_id:0,space:[0,1],spaghetti:1,spaghetticommand:1,spawn_audiosourc:0,special:[0,1],specif:[0,1],specifi:[0,1],splitstr:0,sqlalchemi:[0,1],squar:1,stai:1,start:[0,1],starting_class:0,statement:0,static_fold:0,static_url_path:0,statu:0,stop:1,store:[0,1],str:[0,1],stream:0,string:[0,2],subdomain:0,submodul:0,success:0,support:[0,1],syntax:[0,1],tabl:[0,1],take:1,task:[0,1],telegram:[0,1],telegram_escap:0,telegrambot:0,tell:1,template_fold:0,temporar:0,text:0,than:[0,1],thei:[0,1],them:[0,1],therefor:0,thi:[0,1,3],thing:1,think:1,thought:1,through:[0,1],time:[0,1],titl:0,to_dict:0,to_discord_emb:0,to_json_byt:0,to_json_str:0,too:[0,1],toomanyfounderror:0,tri:1,tupl:[0,1],two:0,type:[0,1],underscor:0,undescrib:0,unexpect:0,unexpectedli:0,union:[0,1],unregister_keyboard_kei:0,unregister_net_handl:0,unregisterederror:0,unsupportederror:[0,1],until:[0,1,3],updat:0,update_activity_with_source_titl:0,update_info:0,uri:0,url:0,url_default:0,url_prefix:0,use:[0,1],used:[0,1],useful:[0,1],user:[0,1],usernam:[0,1],uses:0,using:[0,1],usual:0,utf8:0,util:[1,2],uuid:0,valid:0,valu:0,variabl:0,variou:0,via:2,video:0,voic:0,wai:[0,1],wait:0,want:[0,1],wasn:1,web:2,websit:1,websocket:0,websocketserverprotocol:0,weird:0,welcom:2,went:0,were:[0,1],what:1,when:[0,1],whenev:1,where:0,which:[0,1],why:0,without:0,won:1,word:0,work:[0,1],worth:0,would:0,wrap:1,wrapper:0,written:[0,3],wrong:0,year:0,yet:[0,3],you:[0,1],your:1,youtub:0,youtube_dl:0,youtubedl:0,ytdl_arg:0,ytdl_file:0,ytdldateformat:0,ytdldiscord:0,ytdlfile:0,ytdlinfo:0,ytdlmp3:0,yyyi:0,yyyymmdd:0},titles:["API Reference","Royalnet Commands","royalnet","Running Royalnet"],titleterms:{"new":1,Adding:1,Using:1,access:1,alchemi:1,api:0,argument:1,audio:0,bot:[0,1],code:1,command:[0,1],comun:1,coroutin:1,creat:1,databas:[0,1],delet:1,direct:1,error:0,express:1,fetch:1,filter:1,full:1,initi:1,invok:1,link:2,messag:1,more:1,network:0,oper:1,option:1,order:1,queri:1,refer:0,regular:1,result:1,royalnet:[1,2,3],run:[1,3],slow:1,some:2,string:1,useful:2,util:0,via:1,web:0}}) \ No newline at end of file +Search.setIndex({docnames:["apireference","creatingacommand","index","runningroyalnet"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["apireference.rst","creatingacommand.rst","index.rst","runningroyalnet.rst"],objects:{"royalnet.audio":{FileAudioSource:[0,1,1,""],YtdlDiscord:[0,1,1,""],YtdlFile:[0,1,1,""],YtdlInfo:[0,1,1,""],YtdlMp3:[0,1,1,""]},"royalnet.audio.FileAudioSource":{is_opus:[0,2,1,""],read:[0,2,1,""]},"royalnet.audio.YtdlDiscord":{"delete":[0,2,1,""],convert_to_pcm:[0,2,1,""],create_from_url:[0,2,1,""],info:[0,2,1,""],pcm_available:[0,2,1,""],ready_up:[0,2,1,""],spawn_audiosource:[0,2,1,""]},"royalnet.audio.YtdlFile":{"delete":[0,2,1,""],_default_ytdl_args:[0,3,1,""],download_file:[0,2,1,""],download_from_url:[0,2,1,""],has_info:[0,2,1,""],is_downloaded:[0,2,1,""],open:[0,2,1,""],update_info:[0,2,1,""]},"royalnet.audio.YtdlInfo":{__init__:[0,2,1,""],_default_ytdl_args:[0,3,1,""],retrieve_for_url:[0,2,1,""],to_discord_embed:[0,2,1,""]},"royalnet.audio.YtdlMp3":{"delete":[0,2,1,""],convert_to_mp3:[0,2,1,""],create_and_ready_from_url:[0,2,1,""],create_from_url:[0,2,1,""],info:[0,2,1,""],pcm_available:[0,2,1,""],ready_up:[0,2,1,""]},"royalnet.bots":{DiscordBot:[0,1,1,""],GenericBot:[0,1,1,""],TelegramBot:[0,1,1,""]},"royalnet.bots.DiscordBot":{_bot_factory:[0,2,1,""],_data_factory:[0,2,1,""],_init_client:[0,2,1,""],_init_voice:[0,2,1,""],_initialize:[0,2,1,""],_interface_factory:[0,2,1,""],add_to_music_data:[0,2,1,""],advance_music_data:[0,2,1,""],interface_name:[0,3,1,""],run:[0,2,1,""],update_activity_with_source_title:[0,2,1,""]},"royalnet.bots.GenericBot":{_data_factory:[0,2,1,""],_init_commands:[0,2,1,""],_init_database:[0,2,1,""],_init_loop:[0,2,1,""],_init_network:[0,2,1,""],_init_sentry:[0,2,1,""],_initialize:[0,2,1,""],_interface_factory:[0,2,1,""],_network_handler:[0,2,1,""],get_secret:[0,2,1,""],interface_name:[0,3,1,""],run:[0,2,1,""],run_blocking:[0,2,1,""],set_secret:[0,2,1,""]},"royalnet.bots.TelegramBot":{_data_factory:[0,2,1,""],_handle_callback_query:[0,2,1,""],_handle_message:[0,2,1,""],_handle_update:[0,2,1,""],_init_client:[0,2,1,""],_initialize:[0,2,1,""],_interface_factory:[0,2,1,""],interface_name:[0,3,1,""],run:[0,2,1,""],safe_api_call:[0,2,1,""]},"royalnet.commands":{Command:[0,1,1,""],CommandArgs:[0,1,1,""],CommandData:[0,1,1,""],CommandError:[0,4,1,""],CommandInterface:[0,1,1,""],InvalidInputError:[0,4,1,""],KeyboardExpiredError:[0,4,1,""],UnsupportedError:[0,4,1,""]},"royalnet.commands.Command":{aliases:[0,3,1,""],description:[0,3,1,""],name:[0,3,1,""],require_alchemy_tables:[0,3,1,""],run:[0,2,1,""],syntax:[0,3,1,""]},"royalnet.commands.CommandArgs":{__getitem__:[0,2,1,""],joined:[0,2,1,""],match:[0,2,1,""],optional:[0,2,1,""]},"royalnet.commands.CommandData":{delete_invoking:[0,2,1,""],get_author:[0,2,1,""],keyboard:[0,2,1,""],reply:[0,2,1,""]},"royalnet.commands.CommandInterface":{alchemy:[0,3,1,""],bot:[0,3,1,""],loop:[0,3,1,""],name:[0,3,1,""],net_request:[0,2,1,""],prefix:[0,3,1,""],register_keyboard_key:[0,2,1,""],register_net_handler:[0,2,1,""],unregister_keyboard_key:[0,2,1,""],unregister_net_handler:[0,2,1,""]},"royalnet.database":{Alchemy:[0,1,1,""],DatabaseConfig:[0,1,1,""],relationshiplinkchain:[0,5,1,""]},"royalnet.database.Alchemy":{__init__:[0,2,1,""],_create_tables:[0,2,1,""],session_acm:[0,2,1,""],session_cm:[0,2,1,""]},"royalnet.error":{RoyalnetRequestError:[0,4,1,""],RoyalnetResponseError:[0,4,1,""]},"royalnet.error.RoyalnetRequestError":{args:[0,2,1,""]},"royalnet.network":{ConnectionClosedError:[0,4,1,""],NetworkConfig:[0,1,1,""],NetworkError:[0,4,1,""],NetworkLink:[0,1,1,""],NetworkServer:[0,1,1,""],NotConnectedError:[0,4,1,""],NotIdentifiedError:[0,4,1,""],Package:[0,1,1,""],Request:[0,1,1,""],Response:[0,1,1,""],ResponseError:[0,1,1,""],ResponseSuccess:[0,1,1,""]},"royalnet.network.NetworkLink":{connect:[0,2,1,""],identify:[0,2,1,""],receive:[0,2,1,""],request:[0,2,1,""],run:[0,2,1,""],send:[0,2,1,""]},"royalnet.network.NetworkServer":{find_client:[0,2,1,""],find_destination:[0,2,1,""],listener:[0,2,1,""],route_package:[0,2,1,""],run_blocking:[0,2,1,""],serve:[0,2,1,""]},"royalnet.network.Package":{__init__:[0,2,1,""],from_dict:[0,2,1,""],from_json_bytes:[0,2,1,""],from_json_string:[0,2,1,""],reply:[0,2,1,""],to_dict:[0,2,1,""],to_json_bytes:[0,2,1,""],to_json_string:[0,2,1,""]},"royalnet.network.Request":{from_dict:[0,2,1,""],to_dict:[0,2,1,""]},"royalnet.network.Response":{from_dict:[0,2,1,""],raise_on_error:[0,2,1,""],to_dict:[0,2,1,""]},"royalnet.network.ResponseError":{raise_on_error:[0,2,1,""]},"royalnet.network.ResponseSuccess":{raise_on_error:[0,2,1,""]},"royalnet.utils":{NetworkHandler:[0,1,1,""],andformat:[0,5,1,""],asyncify:[0,5,1,""],cdj:[0,5,1,""],discord_escape:[0,5,1,""],fileformat:[0,5,1,""],numberemojiformat:[0,5,1,""],ordinalformat:[0,5,1,""],parse_5etools_entry:[0,5,1,""],plusformat:[0,5,1,""],safeformat:[0,5,1,""],sleep_until:[0,5,1,""],splitstring:[0,5,1,""],telegram_escape:[0,5,1,""],ytdldateformat:[0,5,1,""]},"royalnet.utils.NetworkHandler":{message_type:[0,3,1,""]},"royalnet.web":{Royalprint:[0,1,1,""],create_app:[0,5,1,""]},royalnet:{audio:[0,0,0,"-"],bots:[0,0,0,"-"],commands:[0,0,0,"-"],database:[0,0,0,"-"],error:[0,0,0,"-"],network:[0,0,0,"-"],utils:[0,0,0,"-"],web:[0,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","attribute","Python attribute"],"4":["py","exception","Python exception"],"5":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:attribute","4":"py:exception","5":"py:function"},terms:{"20m":0,"byte":0,"class":[0,1],"default":[0,1],"final":0,"function":[0,1],"import":1,"int":0,"new":[0,2,3],"null":0,"return":[0,1],"short":1,"static":[0,1],"super":1,"true":[0,1],"try":[0,1],"while":[0,1],Adding:2,And:1,For:1,Not:1,That:0,The:[0,1,2],Then:[0,1,3],These:[0,1],Use:[0,1],Using:2,__default__:[0,3],__dict__:0,__doc__:0,__getitem__:0,__init__:[0,1],__module__:0,__slots__:0,__weakref__:0,_bot_factori:0,_create_t:0,_data_factori:0,_default_ytdl_arg:0,_handle_callback_queri:0,_handle_messag:0,_handle_upd:0,_init_cli:0,_init_command:0,_init_databas:0,_init_loop:0,_init_network:0,_init_sentri:0,_init_voic:0,_initi:0,_interface_factori:0,_network_handl:0,aaaaaa:3,aaaaaaaaaaaaaaa:3,aaaaaaaaaaaaaaaaaaaaaaaa:3,aaaaaaaaaaaaaaaaaaaaaaaaaaa:3,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:3,abl:[0,3],about:[0,1],abstracteventloop:0,access:[0,2],accur:0,add:[0,1],add_to_music_data:0,added:[0,1],adding:[0,1],addit:0,addition:1,address:0,advance_music_data:0,after:1,akin:0,alchemi:[0,2],alia:0,alias:0,all:[0,1,3],allow:[0,1],allowed_pasta:1,along:[0,1],alreadi:[1,3],also:[0,1],altern:0,alwai:[0,1],amount:0,andformat:0,ani:[0,1],anonym:3,anoth:0,anymor:0,anystr:0,api:[2,3],app:0,append:1,applic:[0,3],arg:[0,1],argument:[0,2],around:0,arrai:0,ascend:1,ask:3,associ:3,async:[0,1],asyncifi:[0,1],asyncio:[0,1],asyncron:0,attempt:0,attribut:[0,1],audio:2,audiosourc:0,author:[0,3],autocomplet:0,automat:0,avail:[0,1],avoid:1,await:1,back:1,banana:1,base:0,becaus:1,been:0,befor:1,begin:1,being:[0,1],between:[0,1],block:0,blockingli:0,blueprint:0,bool:0,bot:2,botfath:3,both:0,bracket:1,brief:1,bufferediobas:0,bug:1,call:[0,1],callabl:0,callback:0,can:[0,1,3],cancel:0,cannot:0,carb:1,carbonara:1,caus:1,caution:0,cdj:0,certain:1,chain:0,chang:[0,1],change_pres:0,channel:0,charact:0,chat:[0,1],check:0,class_:0,classmethod:0,client:0,close:[0,1],clue:0,code:[0,2],column:1,come:1,command:2,commandarg:[0,1],commanddata:[0,1],commanderror:[0,1],commandinterfac:0,commun:0,comparis:1,complet:0,complex:1,compon:0,comun:2,config:0,config_obj:0,configur:[0,3],connect:[0,1,3],connectedcli:0,connectionclosederror:0,consid:1,consum:0,contain:[0,1],context:0,convers:0,convert:0,convert_to_mp3:0,convert_to_pcm:0,core:0,coroutin:[0,2],correct:0,correspond:0,cosafaunapesuunafoglia:3,could:1,creat:[0,2,3],create_and_ready_from_url:0,create_app:0,create_from_url:0,current:[0,1],custom:0,daemon:3,dai:0,data:[0,1],databas:2,database_config:0,database_uri:0,databaseconfig:0,date:0,datetim:0,db_path:0,declar:0,def:1,delet:[0,2],delete_invok:[0,1],dent:1,desc:1,descend:1,describ:0,descript:[0,1],desir:3,desktop:3,destin:0,destination_conv_id:0,detail:0,develop:3,dfile:0,dict:0,dictionari:0,differ:1,direct:2,directli:1,discord:[0,3],discord_escap:0,discordbot:0,discordcli:0,displai:[0,1],doc:0,docstr:0,document:[1,2],doe:[0,1],doesn:0,don:[0,1],done:1,download:[0,3],download_1_terabyte_of_spaghetti:1,download_fil:0,download_from_url:0,dsn:3,dure:0,dynam:0,eas:0,edit:0,effect:1,either:0,element:[0,1],els:1,emb:0,emoji:1,empti:0,encod:0,end:0,ending_class:0,engin:[0,1],enough:1,ensur:0,enter:3,entri:0,envvar:0,epoch:0,error:2,error_data:0,error_if_non:0,error_if_unavail:[0,1],escap:0,establish:1,even:0,event:0,everi:[0,1],everyth:0,exact:1,exampl:[0,1],except:[0,1],execut:[0,1],exist:[0,1],expect:1,expir:0,explain:0,express:2,ext:0,extern:1,extra_info:0,extract:0,extract_info:0,fals:0,featur:[0,1],fetch:[0,2],field:1,file:[0,1],fileaudiosourc:0,fileformat:0,filenam:0,filter:2,find:0,find_client:0,find_destin:0,finish:1,first:[1,3],flag:0,flask:0,follow:0,format:0,found:[0,1],from:[0,1,3],from_dict:0,from_json_byt:0,from_json_str:0,from_object:0,full:2,fullfil:0,gener:0,genericbot:0,get:[0,1,3],get_author:0,get_secret:0,github:2,greater:[0,1],group:[0,1,3],guild:0,handl:0,handler:0,has:[0,1],has_info:0,have:[0,1,3],headless:3,here:1,host:3,how:0,html:0,http:0,identifi:0,identity_column_nam:0,identity_t:0,ifi:0,ignor:[0,1,3],ignoreerror:0,imag:1,imgur:3,import_nam:0,incom:0,incomplet:0,index:[0,2],info:0,inform:0,inherit:[0,1],initi:[0,2],input:0,insid:1,instal:3,instanc:[0,1,3],instead:[0,1],interact:1,interfac:[0,1],interface_nam:0,invalid:[0,1],invalidinputerror:[0,1],invok:[0,2],is_download:0,is_open:1,is_opu:0,isn:[0,1],itali:1,item:0,join:[0,1],json:0,jsonabl:0,just:[0,1,3],keep:1,kei:[0,3],key_nam:0,keyboard:0,keyboardexpirederror:0,keyr:2,keyword:1,kind:1,kitchen:1,kwarg:0,last:0,later:1,launch:3,less:0,like:[0,1],link:0,link_typ:0,linux:3,list:[0,1],listen:0,local:3,login:0,look:1,loop:0,made:0,mai:1,main:0,mainli:0,maintain:0,make:[0,1],manag:0,manual:[1,3],mark:0,markup:0,master_secret:0,master_t:0,master_uri:0,match:[0,1],max:0,mean:[0,1],meantim:1,member:1,memori:0,mention:1,messag:[0,2],message_typ:0,metadata:1,method:[0,1],middl:0,might:0,minimum:[0,1],miscellan:0,miss:[0,1],month:0,more:[0,2],multipl:[0,1],music_data:0,must:0,name:[0,1,3],necessari:3,need:[0,1,3],net_request:0,network:[2,3],network_config:0,network_handl:0,networkconfig:0,networkerror:0,networkhandl:0,networklink:0,networkserv:0,next:[0,1,3],nid:0,no_warn:0,nobodi:0,node:0,non:0,none:[0,1],noplaylist:0,normal:1,notat:0,notconnectederror:0,noth:[0,1],notic:1,notidentifiederror:0,notimpl:0,now:[1,3],number:[0,1],numberemojiformat:0,object:[0,1],offset:0,onc:[0,1],one:[0,1,3],one_or_non:1,ones:[0,1],onli:[0,1],open:0,oper:2,option:[0,2],optional_arg:0,opu:0,order:2,order_bi:1,ordinalformat:0,org:0,orm:1,other:[0,1],otherwis:[0,1],outdat:0,output:0,outtmpl:0,overrid:1,packag:[0,3],packet:0,page:0,paramet:[0,1],parenthes:1,parse_5etools_entri:0,part:1,pass:[0,1],password:[0,3],pasta:1,path:0,pattern:[0,1],pcm:0,pcm_avail:0,pickl:0,ping:1,pingcommand:1,pip:3,plai:[0,1],plusformat:0,pong:1,port:0,portal:3,possibl:[0,1],postgresql:1,prefix:0,prepar:0,prepend:0,presenc:0,present:1,press:[0,3],previou:1,previous:1,probabl:[0,3],prompt:3,properli:1,properti:0,python:[1,3],queri:2,quiet:0,rais:[0,2],raise_on_error:0,read:[0,1],readi:[1,3],ready_up:0,realli:0,receiv:[0,1],reciev:0,recogn:1,recreat:0,redisplai:1,refer:[1,2],regex:0,regist:[0,3],register_keyboard_kei:0,register_net_handl:0,regular:2,relat:0,relationshiplinkchain:0,rememb:[1,3],remov:0,replac:0,repli:[0,1],request:[0,1],request_dict:0,request_handl:0,requested_pasta:1,requestedpasta:1,requir:[0,1],require_alchemy_t:[0,1],require_at_least:[0,1],required_arg:0,required_secret:0,required_t:0,requrire_alchemy_t:1,respect:1,respons:0,responseerror:0,responsesuccess:0,rest:[1,3],restart:1,result:[0,2],retriev:0,retrieve_for_url:0,right_now:1,role:1,root_path:0,round:1,rout:0,route_packag:0,row:[0,1],royal:1,royalcod:0,royalgam:3,royalnet:0,royalnetlink:0,royalnetrequesterror:0,royalnetresponseerror:0,royalprint:0,royalt:1,run:[0,2],run_block:0,safe_api_cal:0,safeformat:0,same:1,script:1,second:0,secret:[0,3],secret_kei:0,secrets_nam:0,section:1,select:0,self:[0,1],send:[0,1],sent:[0,1],sentri:3,sentry_dsn:0,separ:[0,1],sequenc:0,serv:0,server:0,session:1,session_acm:0,session_cm:0,set:[0,1],set_secret:0,setup:3,share:1,should:[0,1,3],side:1,signal:0,simpl:1,singl:[0,1],sleep:1,sleep_until:0,slow:2,small:[0,1],some:1,someth:0,somewher:0,song:0,soon:1,sort:1,sourc:0,source_conv_id:0,space:[0,1],spaghetti:1,spaghetticommand:1,spawn_audiosourc:0,special:[0,1],specif:[0,1],specifi:[0,1],splitstr:0,sqlalchemi:[0,1],squar:1,stai:1,start:[0,1,3],starting_class:0,statement:0,static_fold:0,static_url_path:0,statu:0,stop:1,store:1,str:[0,1],stream:0,string:[0,2],subdomain:0,submodul:0,success:0,suppli:3,support:[0,1],syntax:[0,1],system:3,tabl:[0,1],take:1,task:[0,1],telegram:[0,1,3],telegram_escap:0,telegrambot:0,tell:[0,1],template_fold:0,text:0,than:[0,1],thei:[0,1],them:[0,1],thi:[0,1,3],thing:1,think:1,thought:1,through:[0,1],time:[0,1],titl:0,to_dict:0,to_discord_emb:0,to_json_byt:0,to_json_str:0,todo:3,token:3,too:1,tri:1,tupl:[0,1],two:0,type:[0,1,3],underscor:0,undescrib:0,unexpect:0,unexpectedli:0,union:[0,1],unlock:3,unregister_keyboard_kei:0,unregister_net_handl:0,unsupportederror:[0,1],until:[0,1],updat:0,update_activity_with_source_titl:0,update_info:0,uri:0,url:0,url_default:0,url_prefix:0,usag:3,use:[0,1],used:[0,1,3],useful:[0,1],user:[0,1,3],usernam:[0,1],uses:0,using:[0,1],usual:0,utf8:0,util:[1,2],uuid:0,valid:0,valu:0,variabl:0,variou:0,verbos:0,via:2,video:0,voic:0,wai:[0,1],wait:0,want:[0,1],wasn:1,web:2,websit:1,websocket:0,websocketserverprotocol:0,weird:0,welcom:2,went:0,were:[0,1],what:[0,1],when:[0,1],whenev:1,where:0,which:[0,1],why:0,window:3,without:[0,3],wizard:3,won:[1,3],word:0,work:[0,1],worth:0,wrap:1,wrapper:0,written:0,wrong:0,year:0,yet:0,you:[0,1,3],your:[1,3],youtub:0,youtube_dl:0,youtubedl:0,ytdl_arg:0,ytdl_file:0,ytdldateformat:0,ytdldiscord:0,ytdlfile:0,ytdlinfo:0,ytdlmp3:0,yyyi:0,yyyymmdd:0},titles:["API Reference","Royalnet Commands","royalnet","Running Royalnet"],titleterms:{"new":1,Adding:1,The:3,Using:1,access:1,alchemi:1,api:0,argument:1,audio:0,bot:[0,1,3],code:1,command:[0,1],comun:1,coroutin:1,creat:1,databas:[0,1],delet:1,direct:1,error:[0,1],express:1,fetch:1,filter:1,full:1,initi:1,invok:1,keyr:3,link:2,messag:1,more:1,network:0,oper:1,option:1,order:1,queri:1,rais:1,refer:0,regular:1,result:1,royalnet:[1,2,3],run:[1,3],slow:1,some:2,string:1,useful:2,util:0,via:1,web:0}}) \ No newline at end of file diff --git a/docs_source/creatingacommand.rst b/docs_source/creatingacommand.rst index 248a6068..037557cd 100644 --- a/docs_source/creatingacommand.rst +++ b/docs_source/creatingacommand.rst @@ -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 `_. 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 --------------------------------------------- diff --git a/docs_source/runningroyalnet.rst b/docs_source/runningroyalnet.rst index 91376c26..519fe1a0 100644 --- a/docs_source/runningroyalnet.rst +++ b/docs_source/runningroyalnet.rst @@ -3,4 +3,60 @@ Running Royalnet ==================================== -This documentation page hasn't been written yet, please refer to the README until then. \ No newline at end of file +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 +`_. + +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 `_. :: + + 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 `_. :: + + Discord Bot API token []: AAAAAAAAAAAAAAAAAAAAAAAA.AAAAAA.AAAAAAAAAAAAAAAAAAAAAAAAAAA + +Now the configurator will ask you for a Imgur API token. +`Register an application `_ 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 \ No newline at end of file diff --git a/royalnet/bots/discord.py b/royalnet/bots/discord.py index f88a2393..a17feffe 100644 --- a/royalnet/bots/discord.py +++ b/royalnet/bots/discord.py @@ -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 diff --git a/royalnet/bots/generic.py b/royalnet/bots/generic.py index 2c6163a2..0378834a 100644 --- a/royalnet/bots/generic.py +++ b/royalnet/bots/generic.py @@ -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) diff --git a/royalnet/bots/telegram.py b/royalnet/bots/telegram.py index 3c102d1e..1d03a8ba 100644 --- a/royalnet/bots/telegram.py +++ b/royalnet/bots/telegram.py @@ -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 diff --git a/royalnet/commands/royalgames/__init__.py b/royalnet/commands/royalgames/__init__.py index aa8b0f85..7485c79c 100644 --- a/royalnet/commands/royalgames/__init__.py +++ b/royalnet/commands/royalgames/__init__.py @@ -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, diff --git a/royalnet/commands/royalgames/cv.py b/royalnet/commands/royalgames/cv.py index 79b22760..585997b7 100644 --- a/royalnet/commands/royalgames/cv.py +++ b/royalnet/commands/royalgames/cv.py @@ -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) diff --git a/royalnet/commands/royalgames/diario.py b/royalnet/commands/royalgames/diario.py index f12a9b12..0a1ffee6 100644 --- a/royalnet/commands/royalgames/diario.py +++ b/royalnet/commands/royalgames/diario.py @@ -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"] diff --git a/royalnet/commands/royalgames/mm.py b/royalnet/commands/royalgames/mm.py index f3b130c2..4569036a 100644 --- a/royalnet/commands/royalgames/mm.py +++ b/royalnet/commands/royalgames/mm.py @@ -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__) diff --git a/royalnet/commands/royalgames/reminder.py b/royalnet/commands/royalgames/reminder.py index c7148b13..ba914756 100644 --- a/royalnet/commands/royalgames/reminder.py +++ b/royalnet/commands/royalgames/reminder.py @@ -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, diff --git a/royalnet/commands/royalgames/trivia.py b/royalnet/commands/royalgames/trivia.py index b6385af2..b29da884 100644 --- a/royalnet/commands/royalgames/trivia.py +++ b/royalnet/commands/royalgames/trivia.py @@ -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"])}' diff --git a/royalnet/commands/royalgames/videochannel.py b/royalnet/commands/royalgames/videochannel.py index 3d153675..ab58bb81 100644 --- a/royalnet/commands/royalgames/videochannel.py +++ b/royalnet/commands/royalgames/videochannel.py @@ -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: \n[b]Attenzione: la modalità video non funziona su Discord per Android e iOS![/b]") diff --git a/royalnet/commands/royalgames/zawarudo.py b/royalnet/commands/royalgames/zawarudo.py index 3918ab93..d85b644c 100644 --- a/royalnet/commands/royalgames/zawarudo.py +++ b/royalnet/commands/royalgames/zawarudo.py @@ -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 diff --git a/royalnet/commands/royalmusic/__init__.py b/royalnet/commands/royalmusic/__init__.py index e69de29b..c34e0a16 100644 --- a/royalnet/commands/royalmusic/__init__.py +++ b/royalnet/commands/royalmusic/__init__.py @@ -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] diff --git a/royalnet/commands/royalmusic/pause.py b/royalnet/commands/royalmusic/pause.py index 00f42d29..57c16510 100644 --- a/royalnet/commands/royalmusic/pause.py +++ b/royalnet/commands/royalmusic/pause.py @@ -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: diff --git a/royalnet/commands/royalmusic/play.py b/royalnet/commands/royalmusic/play.py index dfcc6eae..b8494fe3 100644 --- a/royalnet/commands/royalmusic/play.py +++ b/royalnet/commands/royalmusic/play.py @@ -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.") diff --git a/royalnet/commands/royalmusic/playmode.py b/royalnet/commands/royalmusic/playmode.py index 989d7d95..bd1a514d 100644 --- a/royalnet/commands/royalmusic/playmode.py +++ b/royalnet/commands/royalmusic/playmode.py @@ -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() diff --git a/royalnet/commands/royalmusic/queue.py b/royalnet/commands/royalmusic/queue.py index 04865bad..a6f94979 100644 --- a/royalnet/commands/royalmusic/queue.py +++ b/royalnet/commands/royalmusic/queue.py @@ -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: diff --git a/royalnet/commands/royalmusic/skip.py b/royalnet/commands/royalmusic/skip.py index 74201e42..2b86058b 100644 --- a/royalnet/commands/royalmusic/skip.py +++ b/royalnet/commands/royalmusic/skip.py @@ -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() diff --git a/royalnet/commands/royalmusic/soundcloud.py b/royalnet/commands/royalmusic/soundcloud.py new file mode 100644 index 00000000..8950a2c7 --- /dev/null +++ b/royalnet/commands/royalmusic/soundcloud.py @@ -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]") diff --git a/royalnet/commands/royalmusic/summon.py b/royalnet/commands/royalmusic/summon.py index 99ed8733..3d855b4c 100644 --- a/royalnet/commands/royalmusic/summon.py +++ b/royalnet/commands/royalmusic/summon.py @@ -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() diff --git a/royalnet/commands/royalmusic/youtube.py b/royalnet/commands/royalmusic/youtube.py new file mode 100644 index 00000000..99fae98e --- /dev/null +++ b/royalnet/commands/royalmusic/youtube.py @@ -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]") diff --git a/royalnet/database/alchemy.py b/royalnet/database/alchemy.py index 74a3a499..2f164368 100644 --- a/royalnet/database/alchemy.py +++ b/royalnet/database/alchemy.py @@ -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: