1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-24 03:54:20 +00:00

Merge pull request #95 from Steffo99/yoshi-refactor

BEEEEEG UPDATE
This commit is contained in:
Steffo 2019-10-19 12:56:49 +02:00 committed by GitHub
commit 5fb3bbd107
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
170 changed files with 829 additions and 22244 deletions

View file

@ -5,11 +5,3 @@ The fifth rewrite of the Royal Network!
It has a lot of submodules, many of which may be used in other bots. It has a lot of submodules, many of which may be used in other bots.
[Documentation available here](https://royal-games.github.io/royalnet/html/index.html). [Documentation available here](https://royal-games.github.io/royalnet/html/index.html).
## Quick install
```
pip3.7 install royalnet
python3.7 -m royalnet.configurator
python3.7 -m royalnet -c (YOUR_COMMAND_PACK)
```

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,4 +0,0 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: b57ffa99137bc3f723f5a94d2c38b963
tags: 645f666f9bcd5a90fca523b33c5a78b7

View file

@ -1,70 +0,0 @@
API Reference
====================================
These pages were automatically generated from docstrings in code.
They might be outdated, or incomplete.
Audio
------------------------------------
.. automodule:: royalnet.audio
:members:
:undoc-members:
:private-members:
Bots
------------------------------------
.. automodule:: royalnet.bots
:members:
:undoc-members:
:private-members:
Commands
------------------------------------
.. automodule:: royalnet.commands
:members:
:undoc-members:
:private-members:
Database
------------------------------------
.. automodule:: royalnet.database
:members:
:undoc-members:
:private-members:
Network
------------------------------------
.. automodule:: royalnet.network
:members:
:undoc-members:
:private-members:
Utils
------------------------------------
.. automodule:: royalnet.utils
:members:
:undoc-members:
:private-members:
Web
------------------------------------
.. automodule:: royalnet.web
:members:
:undoc-members:
:private-members:
Error
------------------------------------
.. automodule:: royalnet.error
:members:
:undoc-members:
:private-members:

View file

@ -1,377 +0,0 @@
.. currentmodule:: royalnet.commands
Royalnet Commands
====================================
A Royalnet Command is a small script that is run whenever a specific message is sent to a Royalnet interface.
A Command code looks like this: ::
from royalnet.commands import Command
class PingCommand(Command):
name = "ping"
description = "Play ping-pong with the bot."
def __init__(self, interface):
# This code is run just once, while the bot is starting
super().__init__()
async def run(self, args, data):
# This code is run every time the command is called
await data.reply("Pong!")
Creating a new Command
------------------------------------
First, think of a ``name`` for your command.
It's the name your command will be called with: for example, the "spaghetti" command will be called by typing **/spaghetti** in chat.
Try to keep the name as short as possible, while staying specific enough so no other command will have the same name.
Next, create a new Python file with the ``name`` you have thought of.
The previously mentioned "spaghetti" command should have a file called ``spaghetti.py``.
Then, in the first row of the file, import the :py:class:`Command` class from royalnet, and create a new class inheriting from it: ::
from royalnet.commands import Command
class SpaghettiCommand(Command):
...
Inside the class, override the attributes ``name`` and ``description`` with respectively the **name of the command** and a **small description of what the command will do**: ::
from royalnet.commands import Command
class SpaghettiCommand(Command):
name = "spaghetti"
description = "Send a spaghetti emoji in the chat."
Now override the :py:meth:`Command.run` method, adding the code you want the bot to run when the command is called.
To send a message in the chat the command was called in, you can use the :py:meth:`CommandData.reply` method: ::
from royalnet.commands import Command
class SpaghettiCommand(Command):
name = "spaghetti"
description = "Send a spaghetti emoji in the chat."
async def run(self, args, data):
await data.reply("🍝")
And... it's done! The command is ready to be added to a bot!
Command arguments
------------------------------------
A command can have some arguments passed by the user: for example, on Telegram an user may type `/spaghetti carbonara al-dente`
to pass the :py:class:`str` `"carbonara al-dente"` to the command code.
These arguments can be accessed in multiple ways through the ``args`` parameter passed to the :py:meth:`Command.run`
method.
If you want your command to use arguments, override the ``syntax`` class attribute with a brief description of the
syntax of your command, possibly using (round parentheses) for required arguments and [square brackets] for optional
ones. ::
from royalnet.commands import Command
class SpaghettiCommand(Command):
name = "spaghetti"
description = "Send a spaghetti emoji in the chat."
syntax = "(requestedpasta)"
async def run(self, args, data):
await data.reply(f"🍝 Here's your {args[0]}!")
Direct access
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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 :py:class:`str`.
If you request an argument with a certain number, but the argument does not exist, an
:py:exc:`royalnet.error.InvalidInputError` is raised, making the arguments accessed in this way **required**. ::
args[0]
# "carbonara"
args[1]
# "al-dente"
args[2]
# InvalidInputError() is raised
Optional access
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you don't want arguments to be required, you can access them through the :py:meth:`CommandArgs.optional` method: it
will return :py:const:`None` if the argument wasn't passed, making it **optional**. ::
args.optional(0)
# "carbonara"
args.optional(1)
# "al-dente"
args.optional(2)
# None
You can specify a default result too, so that the method will return it instead of returning :py:const:`None`: ::
args.optional(2, default="banana")
# "banana"
Full string
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want the full argument string, you can use the :py:meth:`CommandArgs.joined` method. ::
args.joined()
# "carbonara al-dente"
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)
# InvalidInputError() is raised
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:`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. ::
args.match(r"(carb\w+)")
# ("carbonara",)
args.match(r"(al-\w+)")
# InvalidInputError() is raised
args.match(r"\s*(al-\w+)")
# ("al-dente",)
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
---------------------------------------------
You can run code while the bot is starting by overriding the :py:meth:`Command.__init__` function.
You should keep the ``super().__init__(interface)`` call at the start of it, so that the :py:class:`Command` instance is
initialized properly, then add your code after it.
You can add fields to the command to keep **shared data between multiple command calls** (but not bot restarts): it may
be useful for fetching external static data and keeping it until the bot is restarted, or to store references to all the
:py:class:`asyncio.Task` started by the bot. ::
from royalnet.commands import Command
class SpaghettiCommand(Command):
name = "spaghetti"
description = "Send a spaghetti emoji in the chat."
syntax = "(pasta)"
def __init__(self, interface):
super().__init__(interface)
self.requested_pasta = []
async def run(self, args, data):
pasta = args[0]
if pasta in self.requested_pasta:
await data.reply(f"⚠️ This pasta was already requested before.")
return
self.requested_pasta.append(pasta)
await data.reply(f"🍝 Here's your {pasta}!")
Coroutines and slow operations
------------------------------------
You may have noticed that in the previous examples we used ``await data.reply("🍝")`` instead of just ``data.reply("🍝")``.
This is because :py:meth:`CommandData.reply` isn't a simple method: it is a coroutine, a special kind of function that
can be executed separately from the rest of the code, allowing the bot to do other things in the meantime.
By adding the ``await`` keyword before the ``data.reply("🍝")``, we tell the bot that it can do other things, like
receiving new messages, while the message is being sent.
You should avoid running slow normal functions inside bot commands, as they will stop the bot from working until they
are finished and may cause bugs in other parts of the code! ::
async def run(self, args, data):
# Don't do this!
image = download_1_terabyte_of_spaghetti("right_now", from="italy")
...
If the slow function you want does not cause any side effect, you can wrap it with the :py:func:`royalnet.utils.asyncify`
function: ::
async def run(self, args, data):
# If the called function has no side effect, you can do this!
image = await asyncify(download_1_terabyte_of_spaghetti, "right_now", from="italy")
...
Avoid using :py:func:`time.sleep` function, as it is considered a slow operation: use instead :py:func:`asyncio.sleep`,
a coroutine that does the same exact thing.
Delete the invoking message
------------------------------------
The invoking message of a command is the message that the user sent that the bot recognized as a command; for example,
the message ``/spaghetti carbonara`` is the invoking message for the ``spaghetti`` command run.
You can have the bot delete the invoking message for a command by calling the :py:class:`CommandData.delete_invoking`
method: ::
async def run(self, args, data):
await data.delete_invoking()
Not all interfaces support deleting messages; by default, if the interface does not support deletions, the call is
ignored.
You can have the method raise an error if the message can't be deleted by setting the ``error_if_unavailable`` parameter
to True: ::
async def run(self, args, data):
try:
await data.delete_invoking(error_if_unavailable=True)
except royalnet.error.UnsupportedError:
await data.reply("🚫 The message could not be deleted.")
else:
await data.reply("✅ The message was deleted!")
Using the database
------------------------------------
Bots can be connected to a PostgreSQL database through a special SQLAlchemy interface called
:py:class:`royalnet.database.Alchemy`.
If the connection is established, the ``self.interface.alchemy`` and ``self.interface.session`` fields will be
available for use in commands.
``self.interface.alchemy`` is an instance of :py:class:`royalnet.database.Alchemy`, which contains the
:py:class:`sqlalchemy.engine.Engine`, metadata and tables, while ``self.interface.session`` is a
:py:class:`sqlalchemy.orm.session.Session`, and can be interacted in the same way as one.
If you want to use :py:class:`royalnet.database.Alchemy` in your command, you should override the
``require_alchemy_tables`` field with the :py:class:`set` of Alchemy tables you need. ::
from royalnet.commands import Command
from royalnet.database.tables import Royal
class SpaghettiCommand(Command):
name = "spaghetti"
description = "Send a spaghetti emoji in the chat."
syntax = "(pasta)"
requrire_alchemy_tables = {Royal}
...
Querying the database
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can :py:class:`sqlalchemy.orm.query.Query` the database using the SQLAlchemy ORM.
The SQLAlchemy tables can be found inside :py:class:`royalnet.database.Alchemy` with the same name they were created
from, if they were specified in ``require_alchemy_tables``. ::
RoyalTable = self.interface.alchemy.Royal
query = self.interface.session.query(RoyalTable)
Adding filters to the query
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can filter the query results with the :py:meth:`sqlalchemy.orm.query.Query.filter` method.
.. note:: Remember to always use a table column as first comparision element, as it won't work otherwise.
::
query = query.filter(RoyalTable.role == "Member")
Ordering the results of a query
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can order the query results in **ascending order** with the :py:meth:`sqlalchemy.orm.query.Query.order_by` method. ::
query = query.order_by(RoyalTable.username)
Additionally, you can append the `.desc()` method to a table column to sort in **descending order**: ::
query = query.order_by(RoyalTable.username.desc())
Fetching the results of a query
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can fetch the query results with the :py:meth:`sqlalchemy.orm.query.Query.all`,
:py:meth:`sqlalchemy.orm.query.Query.first`, :py:meth:`sqlalchemy.orm.query.Query.one` and
:py:meth:`sqlalchemy.orm.query.Query.one_or_none` methods.
Remember to use :py:func:`royalnet.utils.asyncify` when fetching results, as it may take a while!
Use :py:meth:`sqlalchemy.orm.query.Query.all` if you want a :py:class:`list` of **all results**: ::
results: list = await asyncify(query.all)
Use :py:meth:`sqlalchemy.orm.query.Query.first` if you want **the first result** of the list, or :py:const:`None` if
there are no results: ::
result: typing.Union[..., None] = await asyncify(query.first)
Use :py:meth:`sqlalchemy.orm.query.Query.one` if you expect to have **a single result**, and you want the command to
raise an error if any different number of results is returned: ::
result: ... = await asyncify(query.one) # Raises an error if there are no results or more than a result.
Use :py:meth:`sqlalchemy.orm.query.Query.one_or_none` if you expect to have **a single result**, or **nothing**, and
if you want the command to raise an error if the number of results is greater than one. ::
result: typing.Union[..., None] = await asyncify(query.one_or_none) # Raises an error if there is more than a result.
More Alchemy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can read more about :py:mod:`sqlalchemy` at their `website <https://www.sqlalchemy.org/>`_.
Comunicating via Royalnet
------------------------------------
This section will be changed soon; as such, it will not be documented.

View file

@ -1,18 +0,0 @@
royalnet
====================================
Welcome to the documentation of Royalnet!
.. toctree::
:maxdepth: 3
runningroyalnet
creatingacommand
apireference
Some useful links
------------------------------------
* `Royalnet on GitHub <https://github.com/royal-games/royalnet>`_
* :ref:`genindex`

View file

@ -1,72 +0,0 @@
.. currentmodule:: royalnet
Running Royalnet
====================================
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
------------------------------------
You can run the main ``royalnet`` process by running: ::
python3.7 -m royalnet
To see all available options, you can run: ::
python3.7 -m royalnet --help
.. note:: All royalnet options should be specified **after** the word ``royalnet``, or else they will be passed to
the Python interpreter.

View file

@ -1,764 +0,0 @@
/*
* basic.css
* ~~~~~~~~~
*
* Sphinx stylesheet -- basic theme.
*
* :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/* -- main layout ----------------------------------------------------------- */
div.clearer {
clear: both;
}
/* -- relbar ---------------------------------------------------------------- */
div.related {
width: 100%;
font-size: 90%;
}
div.related h3 {
display: none;
}
div.related ul {
margin: 0;
padding: 0 0 0 10px;
list-style: none;
}
div.related li {
display: inline;
}
div.related li.right {
float: right;
margin-right: 5px;
}
/* -- sidebar --------------------------------------------------------------- */
div.sphinxsidebarwrapper {
padding: 10px 5px 0 10px;
}
div.sphinxsidebar {
float: left;
width: 230px;
margin-left: -100%;
font-size: 90%;
word-wrap: break-word;
overflow-wrap : break-word;
}
div.sphinxsidebar ul {
list-style: none;
}
div.sphinxsidebar ul ul,
div.sphinxsidebar ul.want-points {
margin-left: 20px;
list-style: square;
}
div.sphinxsidebar ul ul {
margin-top: 0;
margin-bottom: 0;
}
div.sphinxsidebar form {
margin-top: 10px;
}
div.sphinxsidebar input {
border: 1px solid #98dbcc;
font-family: sans-serif;
font-size: 1em;
}
div.sphinxsidebar #searchbox form.search {
overflow: hidden;
}
div.sphinxsidebar #searchbox input[type="text"] {
float: left;
width: 80%;
padding: 0.25em;
box-sizing: border-box;
}
div.sphinxsidebar #searchbox input[type="submit"] {
float: left;
width: 20%;
border-left: none;
padding: 0.25em;
box-sizing: border-box;
}
img {
border: 0;
max-width: 100%;
}
/* -- search page ----------------------------------------------------------- */
ul.search {
margin: 10px 0 0 20px;
padding: 0;
}
ul.search li {
padding: 5px 0 5px 20px;
background-image: url(file.png);
background-repeat: no-repeat;
background-position: 0 7px;
}
ul.search li a {
font-weight: bold;
}
ul.search li div.context {
color: #888;
margin: 2px 0 0 30px;
text-align: left;
}
ul.keywordmatches li.goodmatch a {
font-weight: bold;
}
/* -- index page ------------------------------------------------------------ */
table.contentstable {
width: 90%;
margin-left: auto;
margin-right: auto;
}
table.contentstable p.biglink {
line-height: 150%;
}
a.biglink {
font-size: 1.3em;
}
span.linkdescr {
font-style: italic;
padding-top: 5px;
font-size: 90%;
}
/* -- general index --------------------------------------------------------- */
table.indextable {
width: 100%;
}
table.indextable td {
text-align: left;
vertical-align: top;
}
table.indextable ul {
margin-top: 0;
margin-bottom: 0;
list-style-type: none;
}
table.indextable > tbody > tr > td > ul {
padding-left: 0em;
}
table.indextable tr.pcap {
height: 10px;
}
table.indextable tr.cap {
margin-top: 10px;
background-color: #f2f2f2;
}
img.toggler {
margin-right: 3px;
margin-top: 3px;
cursor: pointer;
}
div.modindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
div.genindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
/* -- domain module index --------------------------------------------------- */
table.modindextable td {
padding: 2px;
border-collapse: collapse;
}
/* -- general body styles --------------------------------------------------- */
div.body {
min-width: 450px;
max-width: 800px;
}
div.body p, div.body dd, div.body li, div.body blockquote {
-moz-hyphens: auto;
-ms-hyphens: auto;
-webkit-hyphens: auto;
hyphens: auto;
}
a.headerlink {
visibility: hidden;
}
a.brackets:before,
span.brackets > a:before{
content: "[";
}
a.brackets:after,
span.brackets > a:after {
content: "]";
}
h1:hover > a.headerlink,
h2:hover > a.headerlink,
h3:hover > a.headerlink,
h4:hover > a.headerlink,
h5:hover > a.headerlink,
h6:hover > a.headerlink,
dt:hover > a.headerlink,
caption:hover > a.headerlink,
p.caption:hover > a.headerlink,
div.code-block-caption:hover > a.headerlink {
visibility: visible;
}
div.body p.caption {
text-align: inherit;
}
div.body td {
text-align: left;
}
.first {
margin-top: 0 !important;
}
p.rubric {
margin-top: 30px;
font-weight: bold;
}
img.align-left, .figure.align-left, object.align-left {
clear: left;
float: left;
margin-right: 1em;
}
img.align-right, .figure.align-right, object.align-right {
clear: right;
float: right;
margin-left: 1em;
}
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
img.align-default, .figure.align-default {
display: block;
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left;
}
.align-center {
text-align: center;
}
.align-default {
text-align: center;
}
.align-right {
text-align: right;
}
/* -- sidebars -------------------------------------------------------------- */
div.sidebar {
margin: 0 0 0.5em 1em;
border: 1px solid #ddb;
padding: 7px 7px 0 7px;
background-color: #ffe;
width: 40%;
float: right;
}
p.sidebar-title {
font-weight: bold;
}
/* -- topics ---------------------------------------------------------------- */
div.topic {
border: 1px solid #ccc;
padding: 7px 7px 0 7px;
margin: 10px 0 10px 0;
}
p.topic-title {
font-size: 1.1em;
font-weight: bold;
margin-top: 10px;
}
/* -- admonitions ----------------------------------------------------------- */
div.admonition {
margin-top: 10px;
margin-bottom: 10px;
padding: 7px;
}
div.admonition dt {
font-weight: bold;
}
div.admonition dl {
margin-bottom: 0;
}
p.admonition-title {
margin: 0px 10px 5px 0px;
font-weight: bold;
}
div.body p.centered {
text-align: center;
margin-top: 25px;
}
/* -- tables ---------------------------------------------------------------- */
table.docutils {
border: 0;
border-collapse: collapse;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
table.align-default {
margin-left: auto;
margin-right: auto;
}
table caption span.caption-number {
font-style: italic;
}
table caption span.caption-text {
}
table.docutils td, table.docutils th {
padding: 1px 8px 1px 5px;
border-top: 0;
border-left: 0;
border-right: 0;
border-bottom: 1px solid #aaa;
}
table.footnote td, table.footnote th {
border: 0 !important;
}
th {
text-align: left;
padding-right: 5px;
}
table.citation {
border-left: solid 1px gray;
margin-left: 1px;
}
table.citation td {
border-bottom: none;
}
th > p:first-child,
td > p:first-child {
margin-top: 0px;
}
th > p:last-child,
td > p:last-child {
margin-bottom: 0px;
}
/* -- figures --------------------------------------------------------------- */
div.figure {
margin: 0.5em;
padding: 0.5em;
}
div.figure p.caption {
padding: 0.3em;
}
div.figure p.caption span.caption-number {
font-style: italic;
}
div.figure p.caption span.caption-text {
}
/* -- field list styles ----------------------------------------------------- */
table.field-list td, table.field-list th {
border: 0 !important;
}
.field-list ul {
margin: 0;
padding-left: 1em;
}
.field-list p {
margin: 0;
}
.field-name {
-moz-hyphens: manual;
-ms-hyphens: manual;
-webkit-hyphens: manual;
hyphens: manual;
}
/* -- hlist styles ---------------------------------------------------------- */
table.hlist td {
vertical-align: top;
}
/* -- other body styles ----------------------------------------------------- */
ol.arabic {
list-style: decimal;
}
ol.loweralpha {
list-style: lower-alpha;
}
ol.upperalpha {
list-style: upper-alpha;
}
ol.lowerroman {
list-style: lower-roman;
}
ol.upperroman {
list-style: upper-roman;
}
li > p:first-child {
margin-top: 0px;
}
li > p:last-child {
margin-bottom: 0px;
}
dl.footnote > dt,
dl.citation > dt {
float: left;
}
dl.footnote > dd,
dl.citation > dd {
margin-bottom: 0em;
}
dl.footnote > dd:after,
dl.citation > dd:after {
content: "";
clear: both;
}
dl.field-list {
display: grid;
grid-template-columns: fit-content(30%) auto;
}
dl.field-list > dt {
font-weight: bold;
word-break: break-word;
padding-left: 0.5em;
padding-right: 5px;
}
dl.field-list > dt:after {
content: ":";
}
dl.field-list > dd {
padding-left: 0.5em;
margin-top: 0em;
margin-left: 0em;
margin-bottom: 0em;
}
dl {
margin-bottom: 15px;
}
dd > p:first-child {
margin-top: 0px;
}
dd ul, dd table {
margin-bottom: 10px;
}
dd {
margin-top: 3px;
margin-bottom: 10px;
margin-left: 30px;
}
dt:target, span.highlighted {
background-color: #fbe54e;
}
rect.highlighted {
fill: #fbe54e;
}
dl.glossary dt {
font-weight: bold;
font-size: 1.1em;
}
.optional {
font-size: 1.3em;
}
.sig-paren {
font-size: larger;
}
.versionmodified {
font-style: italic;
}
.system-message {
background-color: #fda;
padding: 5px;
border: 3px solid red;
}
.footnote:target {
background-color: #ffa;
}
.line-block {
display: block;
margin-top: 1em;
margin-bottom: 1em;
}
.line-block .line-block {
margin-top: 0;
margin-bottom: 0;
margin-left: 1.5em;
}
.guilabel, .menuselection {
font-family: sans-serif;
}
.accelerator {
text-decoration: underline;
}
.classifier {
font-style: oblique;
}
.classifier:before {
font-style: normal;
margin: 0.5em;
content: ":";
}
abbr, acronym {
border-bottom: dotted 1px;
cursor: help;
}
/* -- code displays --------------------------------------------------------- */
pre {
overflow: auto;
overflow-y: hidden; /* fixes display issues on Chrome browsers */
}
span.pre {
-moz-hyphens: none;
-ms-hyphens: none;
-webkit-hyphens: none;
hyphens: none;
}
td.linenos pre {
padding: 5px 0px;
border: 0;
background-color: transparent;
color: #aaa;
}
table.highlighttable {
margin-left: 0.5em;
}
table.highlighttable td {
padding: 0 0.5em 0 0.5em;
}
div.code-block-caption {
padding: 2px 5px;
font-size: small;
}
div.code-block-caption code {
background-color: transparent;
}
div.code-block-caption + div > div.highlight > pre {
margin-top: 0;
}
div.code-block-caption span.caption-number {
padding: 0.1em 0.3em;
font-style: italic;
}
div.code-block-caption span.caption-text {
}
div.literal-block-wrapper {
padding: 1em 1em 0;
}
div.literal-block-wrapper div.highlight {
margin: 0;
}
code.descname {
background-color: transparent;
font-weight: bold;
font-size: 1.2em;
}
code.descclassname {
background-color: transparent;
}
code.xref, a code {
background-color: transparent;
font-weight: bold;
}
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
background-color: transparent;
}
.viewcode-link {
float: right;
}
.viewcode-back {
float: right;
font-family: sans-serif;
}
div.viewcode-block:target {
margin: -1px -10px;
padding: 0 10px;
}
/* -- math display ---------------------------------------------------------- */
img.math {
vertical-align: middle;
}
div.body div.math p {
text-align: center;
}
span.eqno {
float: right;
}
span.eqno a.headerlink {
position: relative;
left: 0px;
z-index: 1;
}
div.math:hover a.headerlink {
visibility: visible;
}
/* -- printout stylesheet --------------------------------------------------- */
@media print {
div.document,
div.documentwrapper,
div.bodywrapper {
margin: 0 !important;
width: 100%;
}
div.sphinxsidebar,
div.related,
div.footer,
#top-link {
display: none;
}
}

View file

@ -1 +0,0 @@
.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../fonts/fontawesome-webfont.eot");src:url("../fonts/fontawesome-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff") format("woff"),url("../fonts/fontawesome-webfont.ttf") format("truetype"),url("../fonts/fontawesome-webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}

File diff suppressed because one or more lines are too long

View file

@ -1,314 +0,0 @@
/*
* doctools.js
* ~~~~~~~~~~~
*
* Sphinx JavaScript utilities for all documentation.
*
* :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/**
* select a different prefix for underscore
*/
$u = _.noConflict();
/**
* make the code below compatible with browsers without
* an installed firebug like debugger
if (!window.console || !console.firebug) {
var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
"dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
"profile", "profileEnd"];
window.console = {};
for (var i = 0; i < names.length; ++i)
window.console[names[i]] = function() {};
}
*/
/**
* small helper function to urldecode strings
*/
jQuery.urldecode = function(x) {
return decodeURIComponent(x).replace(/\+/g, ' ');
};
/**
* small helper function to urlencode strings
*/
jQuery.urlencode = encodeURIComponent;
/**
* This function returns the parsed url parameters of the
* current request. Multiple values per key are supported,
* it will always return arrays of strings for the value parts.
*/
jQuery.getQueryParameters = function(s) {
if (typeof s === 'undefined')
s = document.location.search;
var parts = s.substr(s.indexOf('?') + 1).split('&');
var result = {};
for (var i = 0; i < parts.length; i++) {
var tmp = parts[i].split('=', 2);
var key = jQuery.urldecode(tmp[0]);
var value = jQuery.urldecode(tmp[1]);
if (key in result)
result[key].push(value);
else
result[key] = [value];
}
return result;
};
/**
* highlight a given string on a jquery object by wrapping it in
* span elements with the given class name.
*/
jQuery.fn.highlightText = function(text, className) {
function highlight(node, addItems) {
if (node.nodeType === 3) {
var val = node.nodeValue;
var pos = val.toLowerCase().indexOf(text);
if (pos >= 0 &&
!jQuery(node.parentNode).hasClass(className) &&
!jQuery(node.parentNode).hasClass("nohighlight")) {
var span;
var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
if (isInSVG) {
span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
} else {
span = document.createElement("span");
span.className = className;
}
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
document.createTextNode(val.substr(pos + text.length)),
node.nextSibling));
node.nodeValue = val.substr(0, pos);
if (isInSVG) {
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
var bbox = node.parentElement.getBBox();
rect.x.baseVal.value = bbox.x;
rect.y.baseVal.value = bbox.y;
rect.width.baseVal.value = bbox.width;
rect.height.baseVal.value = bbox.height;
rect.setAttribute('class', className);
addItems.push({
"parent": node.parentNode,
"target": rect});
}
}
}
else if (!jQuery(node).is("button, select, textarea")) {
jQuery.each(node.childNodes, function() {
highlight(this, addItems);
});
}
}
var addItems = [];
var result = this.each(function() {
highlight(this, addItems);
});
for (var i = 0; i < addItems.length; ++i) {
jQuery(addItems[i].parent).before(addItems[i].target);
}
return result;
};
/*
* backward compatibility for jQuery.browser
* This will be supported until firefox bug is fixed.
*/
if (!jQuery.browser) {
jQuery.uaMatch = function(ua) {
ua = ua.toLowerCase();
var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
/(msie) ([\w.]+)/.exec(ua) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
[];
return {
browser: match[ 1 ] || "",
version: match[ 2 ] || "0"
};
};
jQuery.browser = {};
jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
}
/**
* Small JavaScript module for the documentation.
*/
var Documentation = {
init : function() {
this.fixFirefoxAnchorBug();
this.highlightSearchWords();
this.initIndexTable();
if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) {
this.initOnKeyListeners();
}
},
/**
* i18n support
*/
TRANSLATIONS : {},
PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; },
LOCALE : 'unknown',
// gettext and ngettext don't access this so that the functions
// can safely bound to a different name (_ = Documentation.gettext)
gettext : function(string) {
var translated = Documentation.TRANSLATIONS[string];
if (typeof translated === 'undefined')
return string;
return (typeof translated === 'string') ? translated : translated[0];
},
ngettext : function(singular, plural, n) {
var translated = Documentation.TRANSLATIONS[singular];
if (typeof translated === 'undefined')
return (n == 1) ? singular : plural;
return translated[Documentation.PLURALEXPR(n)];
},
addTranslations : function(catalog) {
for (var key in catalog.messages)
this.TRANSLATIONS[key] = catalog.messages[key];
this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
this.LOCALE = catalog.locale;
},
/**
* add context elements like header anchor links
*/
addContextElements : function() {
$('div[id] > :header:first').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this headline')).
appendTo(this);
});
$('dt[id]').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this definition')).
appendTo(this);
});
},
/**
* workaround a firefox stupidity
* see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075
*/
fixFirefoxAnchorBug : function() {
if (document.location.hash && $.browser.mozilla)
window.setTimeout(function() {
document.location.href += '';
}, 10);
},
/**
* highlight the search words provided in the url in the text
*/
highlightSearchWords : function() {
var params = $.getQueryParameters();
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
if (terms.length) {
var body = $('div.body');
if (!body.length) {
body = $('body');
}
window.setTimeout(function() {
$.each(terms, function() {
body.highlightText(this.toLowerCase(), 'highlighted');
});
}, 10);
$('<p class="highlight-link"><a href="javascript:Documentation.' +
'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
.appendTo($('#searchbox'));
}
},
/**
* init the domain index toggle buttons
*/
initIndexTable : function() {
var togglers = $('img.toggler').click(function() {
var src = $(this).attr('src');
var idnum = $(this).attr('id').substr(7);
$('tr.cg-' + idnum).toggle();
if (src.substr(-9) === 'minus.png')
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
else
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
}).css('display', '');
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
togglers.click();
}
},
/**
* helper function to hide the search marks again
*/
hideSearchWords : function() {
$('#searchbox .highlight-link').fadeOut(300);
$('span.highlighted').removeClass('highlighted');
},
/**
* make the url absolute
*/
makeURL : function(relativeURL) {
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
},
/**
* get the current relative url
*/
getCurrentURL : function() {
var path = document.location.pathname;
var parts = path.split(/\//);
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
if (this === '..')
parts.pop();
});
var url = parts.join('/');
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
},
initOnKeyListeners: function() {
$(document).keyup(function(event) {
var activeElementType = document.activeElement.tagName;
// don't navigate when in search box or textarea
if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') {
switch (event.keyCode) {
case 37: // left
var prevHref = $('link[rel="prev"]').prop('href');
if (prevHref) {
window.location.href = prevHref;
return false;
}
case 39: // right
var nextHref = $('link[rel="next"]').prop('href');
if (nextHref) {
window.location.href = nextHref;
return false;
}
}
}
});
}
};
// quick alias for translations
_ = Documentation.gettext;
$(document).ready(function() {
Documentation.init();
});

View file

@ -1,10 +0,0 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true,
SOURCELINK_SUFFIX: '.txt',
NAVIGATION_WITH_KEYS: false
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 B

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 434 KiB

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,3 +0,0 @@
/* sphinx_rtd_theme version 0.4.3 | MIT license */
/* Built 20190212 16:02 */
require=function r(s,a,l){function c(e,n){if(!a[e]){if(!s[e]){var i="function"==typeof require&&require;if(!n&&i)return i(e,!0);if(u)return u(e,!0);var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}var o=a[e]={exports:{}};s[e][0].call(o.exports,function(n){return c(s[e][1][n]||n)},o,o.exports,r,s,a,l)}return a[e].exports}for(var u="function"==typeof require&&require,n=0;n<l.length;n++)c(l[n]);return c}({"sphinx-rtd-theme":[function(n,e,i){var jQuery="undefined"!=typeof window?window.jQuery:n("jquery");e.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(e){var i=this;void 0===e&&(e=!0),i.isRunning||(i.isRunning=!0,jQuery(function(n){i.init(n),i.reset(),i.win.on("hashchange",i.reset),e&&i.win.on("scroll",function(){i.linkScroll||i.winScroll||(i.winScroll=!0,requestAnimationFrame(function(){i.onScroll()}))}),i.win.on("resize",function(){i.winResize||(i.winResize=!0,requestAnimationFrame(function(){i.onResize()}))}),i.onResize()}))},enableSticky:function(){this.enable(!0)},init:function(i){i(document);var t=this;this.navBar=i("div.wy-side-scroll:first"),this.win=i(window),i(document).on("click","[data-toggle='wy-nav-top']",function(){i("[data-toggle='wy-nav-shift']").toggleClass("shift"),i("[data-toggle='rst-versions']").toggleClass("shift")}).on("click",".wy-menu-vertical .current ul li a",function(){var n=i(this);i("[data-toggle='wy-nav-shift']").removeClass("shift"),i("[data-toggle='rst-versions']").toggleClass("shift"),t.toggleCurrent(n),t.hashChange()}).on("click","[data-toggle='rst-current-version']",function(){i("[data-toggle='rst-versions']").toggleClass("shift-up")}),i("table.docutils:not(.field-list,.footnote,.citation)").wrap("<div class='wy-table-responsive'></div>"),i("table.docutils.footnote").wrap("<div class='wy-table-responsive footnote'></div>"),i("table.docutils.citation").wrap("<div class='wy-table-responsive citation'></div>"),i(".wy-menu-vertical ul").not(".simple").siblings("a").each(function(){var e=i(this);expand=i('<span class="toctree-expand"></span>'),expand.on("click",function(n){return t.toggleCurrent(e),n.stopPropagation(),!1}),e.prepend(expand)})},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),i=e.find('[href="'+n+'"]');if(0===i.length){var t=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(i=e.find('[href="#'+t.attr("id")+'"]')).length&&(i=e.find('[href="#"]'))}0<i.length&&($(".wy-menu-vertical .current").removeClass("current"),i.addClass("current"),i.closest("li.toctree-l1").addClass("current"),i.closest("li.toctree-l1").parent().addClass("current"),i.closest("li.toctree-l1").addClass("current"),i.closest("li.toctree-l2").addClass("current"),i.closest("li.toctree-l3").addClass("current"),i.closest("li.toctree-l4").addClass("current"),i[0].scrollIntoView())}catch(o){console.log("Error expanding nav for anchor",o)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,i=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(i),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",function(){this.linkScroll=!1})},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current"),e.siblings().find("li.current").removeClass("current"),e.find("> ul li.current").removeClass("current"),e.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:e.exports.ThemeNav,StickyNav:e.exports.ThemeNav}),function(){for(var r=0,n=["ms","moz","webkit","o"],e=0;e<n.length&&!window.requestAnimationFrame;++e)window.requestAnimationFrame=window[n[e]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[n[e]+"CancelAnimationFrame"]||window[n[e]+"CancelRequestAnimationFrame"];window.requestAnimationFrame||(window.requestAnimationFrame=function(n,e){var i=(new Date).getTime(),t=Math.max(0,16-(i-r)),o=window.setTimeout(function(){n(i+t)},t);return r=i+t,o}),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(n){clearTimeout(n)})}()},{jquery:"jquery"}]},{},["sphinx-rtd-theme"]);

View file

@ -1,297 +0,0 @@
/*
* language_data.js
* ~~~~~~~~~~~~~~~~
*
* This script contains the language-specific data used by searchtools.js,
* namely the list of stopwords, stemmer, scorer and splitter.
*
* :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"];
/* Non-minified version JS is _stemmer.js if file is provided */
/**
* Porter Stemmer
*/
var Stemmer = function() {
var step2list = {
ational: 'ate',
tional: 'tion',
enci: 'ence',
anci: 'ance',
izer: 'ize',
bli: 'ble',
alli: 'al',
entli: 'ent',
eli: 'e',
ousli: 'ous',
ization: 'ize',
ation: 'ate',
ator: 'ate',
alism: 'al',
iveness: 'ive',
fulness: 'ful',
ousness: 'ous',
aliti: 'al',
iviti: 'ive',
biliti: 'ble',
logi: 'log'
};
var step3list = {
icate: 'ic',
ative: '',
alize: 'al',
iciti: 'ic',
ical: 'ic',
ful: '',
ness: ''
};
var c = "[^aeiou]"; // consonant
var v = "[aeiouy]"; // vowel
var C = c + "[^aeiouy]*"; // consonant sequence
var V = v + "[aeiou]*"; // vowel sequence
var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
var s_v = "^(" + C + ")?" + v; // vowel in stem
this.stemWord = function (w) {
var stem;
var suffix;
var firstch;
var origword = w;
if (w.length < 3)
return w;
var re;
var re2;
var re3;
var re4;
firstch = w.substr(0,1);
if (firstch == "y")
w = firstch.toUpperCase() + w.substr(1);
// Step 1a
re = /^(.+?)(ss|i)es$/;
re2 = /^(.+?)([^s])s$/;
if (re.test(w))
w = w.replace(re,"$1$2");
else if (re2.test(w))
w = w.replace(re2,"$1$2");
// Step 1b
re = /^(.+?)eed$/;
re2 = /^(.+?)(ed|ing)$/;
if (re.test(w)) {
var fp = re.exec(w);
re = new RegExp(mgr0);
if (re.test(fp[1])) {
re = /.$/;
w = w.replace(re,"");
}
}
else if (re2.test(w)) {
var fp = re2.exec(w);
stem = fp[1];
re2 = new RegExp(s_v);
if (re2.test(stem)) {
w = stem;
re2 = /(at|bl|iz)$/;
re3 = new RegExp("([^aeiouylsz])\\1$");
re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
if (re2.test(w))
w = w + "e";
else if (re3.test(w)) {
re = /.$/;
w = w.replace(re,"");
}
else if (re4.test(w))
w = w + "e";
}
}
// Step 1c
re = /^(.+?)y$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
re = new RegExp(s_v);
if (re.test(stem))
w = stem + "i";
}
// Step 2
re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
suffix = fp[2];
re = new RegExp(mgr0);
if (re.test(stem))
w = stem + step2list[suffix];
}
// Step 3
re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
suffix = fp[2];
re = new RegExp(mgr0);
if (re.test(stem))
w = stem + step3list[suffix];
}
// Step 4
re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
re2 = /^(.+?)(s|t)(ion)$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
re = new RegExp(mgr1);
if (re.test(stem))
w = stem;
}
else if (re2.test(w)) {
var fp = re2.exec(w);
stem = fp[1] + fp[2];
re2 = new RegExp(mgr1);
if (re2.test(stem))
w = stem;
}
// Step 5
re = /^(.+?)e$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
re = new RegExp(mgr1);
re2 = new RegExp(meq1);
re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
w = stem;
}
re = /ll$/;
re2 = new RegExp(mgr1);
if (re.test(w) && re2.test(w)) {
re = /.$/;
w = w.replace(re,"");
}
// and turn initial Y back to y
if (firstch == "y")
w = firstch.toLowerCase() + w.substr(1);
return w;
}
}
var splitChars = (function() {
var result = {};
var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648,
1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702,
2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971,
2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345,
3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761,
3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823,
4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125,
8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695,
11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587,
43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141];
var i, j, start, end;
for (i = 0; i < singles.length; i++) {
result[singles[i]] = true;
}
var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709],
[722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161],
[1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568],
[1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807],
[1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047],
[2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383],
[2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450],
[2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547],
[2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673],
[2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820],
[2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946],
[2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023],
[3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173],
[3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332],
[3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481],
[3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718],
[3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791],
[3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095],
[4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205],
[4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687],
[4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968],
[4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869],
[5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102],
[6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271],
[6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592],
[6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822],
[6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167],
[7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959],
[7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143],
[8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318],
[8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483],
[8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101],
[10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567],
[11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292],
[12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444],
[12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783],
[12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311],
[19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511],
[42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774],
[42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071],
[43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263],
[43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519],
[43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647],
[43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967],
[44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295],
[57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274],
[64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007],
[65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381],
[65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]];
for (i = 0; i < ranges.length; i++) {
start = ranges[i][0];
end = ranges[i][1];
for (j = start; j <= end; j++) {
result[j] = true;
}
}
return result;
})();
function splitQuery(query) {
var result = [];
var start = -1;
for (var i = 0; i < query.length; i++) {
if (splitChars[query.charCodeAt(i)]) {
if (start !== -1) {
result.push(query.slice(start, i));
start = -1;
}
} else if (start === -1) {
start = i;
}
}
if (start !== -1) {
result.push(query.slice(start));
}
return result;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 B

View file

@ -1,69 +0,0 @@
.highlight .hll { background-color: #ffffcc }
.highlight { background: #f8f8f8; }
.highlight .c { color: #408080; font-style: italic } /* Comment */
.highlight .err { border: 1px solid #FF0000 } /* Error */
.highlight .k { color: #008000; font-weight: bold } /* Keyword */
.highlight .o { color: #666666 } /* Operator */
.highlight .ch { color: #408080; font-style: italic } /* Comment.Hashbang */
.highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #BC7A00 } /* Comment.Preproc */
.highlight .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */
.highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */
.highlight .cs { color: #408080; font-style: italic } /* Comment.Special */
.highlight .gd { color: #A00000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #00A000 } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #0044DD } /* Generic.Traceback */
.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008000 } /* Keyword.Pseudo */
.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #B00040 } /* Keyword.Type */
.highlight .m { color: #666666 } /* Literal.Number */
.highlight .s { color: #BA2121 } /* Literal.String */
.highlight .na { color: #7D9029 } /* Name.Attribute */
.highlight .nb { color: #008000 } /* Name.Builtin */
.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */
.highlight .no { color: #880000 } /* Name.Constant */
.highlight .nd { color: #AA22FF } /* Name.Decorator */
.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0000FF } /* Name.Function */
.highlight .nl { color: #A0A000 } /* Name.Label */
.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #19177C } /* Name.Variable */
.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #666666 } /* Literal.Number.Bin */
.highlight .mf { color: #666666 } /* Literal.Number.Float */
.highlight .mh { color: #666666 } /* Literal.Number.Hex */
.highlight .mi { color: #666666 } /* Literal.Number.Integer */
.highlight .mo { color: #666666 } /* Literal.Number.Oct */
.highlight .sa { color: #BA2121 } /* Literal.String.Affix */
.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
.highlight .sc { color: #BA2121 } /* Literal.String.Char */
.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */
.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #BA2121 } /* Literal.String.Double */
.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
.highlight .sx { color: #008000 } /* Literal.String.Other */
.highlight .sr { color: #BB6688 } /* Literal.String.Regex */
.highlight .s1 { color: #BA2121 } /* Literal.String.Single */
.highlight .ss { color: #19177C } /* Literal.String.Symbol */
.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0000FF } /* Name.Function.Magic */
.highlight .vc { color: #19177C } /* Name.Variable.Class */
.highlight .vg { color: #19177C } /* Name.Variable.Global */
.highlight .vi { color: #19177C } /* Name.Variable.Instance */
.highlight .vm { color: #19177C } /* Name.Variable.Magic */
.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */

View file

@ -1,3 +0,0 @@
.wy-side-nav-search, .wy-nav-top {
background-color: #0d193b !important;
}

View file

@ -1,506 +0,0 @@
/*
* searchtools.js
* ~~~~~~~~~~~~~~~~
*
* Sphinx JavaScript utilities for the full-text search.
*
* :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
if (!Scorer) {
/**
* Simple result scoring code.
*/
var Scorer = {
// Implement the following function to further tweak the score for each result
// The function takes a result array [filename, title, anchor, descr, score]
// and returns the new score.
/*
score: function(result) {
return result[4];
},
*/
// query matches the full name of an object
objNameMatch: 11,
// or matches in the last dotted part of the object name
objPartialMatch: 6,
// Additive scores depending on the priority of the object
objPrio: {0: 15, // used to be importantResults
1: 5, // used to be objectResults
2: -5}, // used to be unimportantResults
// Used when the priority is not in the mapping.
objPrioDefault: 0,
// query found in title
title: 15,
partialTitle: 7,
// query found in terms
term: 5,
partialTerm: 2
};
}
if (!splitQuery) {
function splitQuery(query) {
return query.split(/\s+/);
}
}
/**
* Search Module
*/
var Search = {
_index : null,
_queued_query : null,
_pulse_status : -1,
htmlToText : function(htmlString) {
var htmlElement = document.createElement('span');
htmlElement.innerHTML = htmlString;
$(htmlElement).find('.headerlink').remove();
docContent = $(htmlElement).find('[role=main]')[0];
return docContent.textContent || docContent.innerText;
},
init : function() {
var params = $.getQueryParameters();
if (params.q) {
var query = params.q[0];
$('input[name="q"]')[0].value = query;
this.performSearch(query);
}
},
loadIndex : function(url) {
$.ajax({type: "GET", url: url, data: null,
dataType: "script", cache: true,
complete: function(jqxhr, textstatus) {
if (textstatus != "success") {
document.getElementById("searchindexloader").src = url;
}
}});
},
setIndex : function(index) {
var q;
this._index = index;
if ((q = this._queued_query) !== null) {
this._queued_query = null;
Search.query(q);
}
},
hasIndex : function() {
return this._index !== null;
},
deferQuery : function(query) {
this._queued_query = query;
},
stopPulse : function() {
this._pulse_status = 0;
},
startPulse : function() {
if (this._pulse_status >= 0)
return;
function pulse() {
var i;
Search._pulse_status = (Search._pulse_status + 1) % 4;
var dotString = '';
for (i = 0; i < Search._pulse_status; i++)
dotString += '.';
Search.dots.text(dotString);
if (Search._pulse_status > -1)
window.setTimeout(pulse, 500);
}
pulse();
},
/**
* perform a search for something (or wait until index is loaded)
*/
performSearch : function(query) {
// create the required interface elements
this.out = $('#search-results');
this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
this.dots = $('<span></span>').appendTo(this.title);
this.status = $('<p class="search-summary">&nbsp;</p>').appendTo(this.out);
this.output = $('<ul class="search"/>').appendTo(this.out);
$('#search-progress').text(_('Preparing search...'));
this.startPulse();
// index already loaded, the browser was quick!
if (this.hasIndex())
this.query(query);
else
this.deferQuery(query);
},
/**
* execute search (requires search index to be loaded)
*/
query : function(query) {
var i;
// stem the searchterms and add them to the correct list
var stemmer = new Stemmer();
var searchterms = [];
var excluded = [];
var hlterms = [];
var tmp = splitQuery(query);
var objectterms = [];
for (i = 0; i < tmp.length; i++) {
if (tmp[i] !== "") {
objectterms.push(tmp[i].toLowerCase());
}
if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i].match(/^\d+$/) ||
tmp[i] === "") {
// skip this "word"
continue;
}
// stem the word
var word = stemmer.stemWord(tmp[i].toLowerCase());
// prevent stemmer from cutting word smaller than two chars
if(word.length < 3 && tmp[i].length >= 3) {
word = tmp[i];
}
var toAppend;
// select the correct list
if (word[0] == '-') {
toAppend = excluded;
word = word.substr(1);
}
else {
toAppend = searchterms;
hlterms.push(tmp[i].toLowerCase());
}
// only add if not already in the list
if (!$u.contains(toAppend, word))
toAppend.push(word);
}
var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
// console.debug('SEARCH: searching for:');
// console.info('required: ', searchterms);
// console.info('excluded: ', excluded);
// prepare search
var terms = this._index.terms;
var titleterms = this._index.titleterms;
// array of [filename, title, anchor, descr, score]
var results = [];
$('#search-progress').empty();
// lookup as object
for (i = 0; i < objectterms.length; i++) {
var others = [].concat(objectterms.slice(0, i),
objectterms.slice(i+1, objectterms.length));
results = results.concat(this.performObjectSearch(objectterms[i], others));
}
// lookup as search terms in fulltext
results = results.concat(this.performTermsSearch(searchterms, excluded, terms, titleterms));
// let the scorer override scores with a custom scoring function
if (Scorer.score) {
for (i = 0; i < results.length; i++)
results[i][4] = Scorer.score(results[i]);
}
// now sort the results by score (in opposite order of appearance, since the
// display function below uses pop() to retrieve items) and then
// alphabetically
results.sort(function(a, b) {
var left = a[4];
var right = b[4];
if (left > right) {
return 1;
} else if (left < right) {
return -1;
} else {
// same score: sort alphabetically
left = a[1].toLowerCase();
right = b[1].toLowerCase();
return (left > right) ? -1 : ((left < right) ? 1 : 0);
}
});
// for debugging
//Search.lastresults = results.slice(); // a copy
//console.info('search results:', Search.lastresults);
// print the results
var resultCount = results.length;
function displayNextItem() {
// results left, load the summary and display it
if (results.length) {
var item = results.pop();
var listItem = $('<li style="display:none"></li>');
if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') {
// dirhtml builder
var dirname = item[0] + '/';
if (dirname.match(/\/index\/$/)) {
dirname = dirname.substring(0, dirname.length-6);
} else if (dirname == 'index/') {
dirname = '';
}
listItem.append($('<a/>').attr('href',
DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
highlightstring + item[2]).html(item[1]));
} else {
// normal html builders
listItem.append($('<a/>').attr('href',
item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
highlightstring + item[2]).html(item[1]));
}
if (item[3]) {
listItem.append($('<span> (' + item[3] + ')</span>'));
Search.output.append(listItem);
listItem.slideDown(5, function() {
displayNextItem();
});
} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
$.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX,
dataType: "text",
complete: function(jqxhr, textstatus) {
var data = jqxhr.responseText;
if (data !== '' && data !== undefined) {
listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
}
Search.output.append(listItem);
listItem.slideDown(5, function() {
displayNextItem();
});
}});
} else {
// no source available, just display title
Search.output.append(listItem);
listItem.slideDown(5, function() {
displayNextItem();
});
}
}
// search finished, update title and status message
else {
Search.stopPulse();
Search.title.text(_('Search Results'));
if (!resultCount)
Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.'));
else
Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
Search.status.fadeIn(500);
}
}
displayNextItem();
},
/**
* search for object names
*/
performObjectSearch : function(object, otherterms) {
var filenames = this._index.filenames;
var docnames = this._index.docnames;
var objects = this._index.objects;
var objnames = this._index.objnames;
var titles = this._index.titles;
var i;
var results = [];
for (var prefix in objects) {
for (var name in objects[prefix]) {
var fullname = (prefix ? prefix + '.' : '') + name;
var fullnameLower = fullname.toLowerCase()
if (fullnameLower.indexOf(object) > -1) {
var score = 0;
var parts = fullnameLower.split('.');
// check for different match types: exact matches of full name or
// "last name" (i.e. last dotted part)
if (fullnameLower == object || parts[parts.length - 1] == object) {
score += Scorer.objNameMatch;
// matches in last name
} else if (parts[parts.length - 1].indexOf(object) > -1) {
score += Scorer.objPartialMatch;
}
var match = objects[prefix][name];
var objname = objnames[match[1]][2];
var title = titles[match[0]];
// If more than one term searched for, we require other words to be
// found in the name/title/description
if (otherterms.length > 0) {
var haystack = (prefix + ' ' + name + ' ' +
objname + ' ' + title).toLowerCase();
var allfound = true;
for (i = 0; i < otherterms.length; i++) {
if (haystack.indexOf(otherterms[i]) == -1) {
allfound = false;
break;
}
}
if (!allfound) {
continue;
}
}
var descr = objname + _(', in ') + title;
var anchor = match[3];
if (anchor === '')
anchor = fullname;
else if (anchor == '-')
anchor = objnames[match[1]][1] + '-' + fullname;
// add custom score for some objects according to scorer
if (Scorer.objPrio.hasOwnProperty(match[2])) {
score += Scorer.objPrio[match[2]];
} else {
score += Scorer.objPrioDefault;
}
results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]);
}
}
}
return results;
},
/**
* search for full-text terms in the index
*/
performTermsSearch : function(searchterms, excluded, terms, titleterms) {
var docnames = this._index.docnames;
var filenames = this._index.filenames;
var titles = this._index.titles;
var i, j, file;
var fileMap = {};
var scoreMap = {};
var results = [];
// perform the search on the required terms
for (i = 0; i < searchterms.length; i++) {
var word = searchterms[i];
var files = [];
var _o = [
{files: terms[word], score: Scorer.term},
{files: titleterms[word], score: Scorer.title}
];
// add support for partial matches
if (word.length > 2) {
for (var w in terms) {
if (w.match(word) && !terms[word]) {
_o.push({files: terms[w], score: Scorer.partialTerm})
}
}
for (var w in titleterms) {
if (w.match(word) && !titleterms[word]) {
_o.push({files: titleterms[w], score: Scorer.partialTitle})
}
}
}
// no match but word was a required one
if ($u.every(_o, function(o){return o.files === undefined;})) {
break;
}
// found search word in contents
$u.each(_o, function(o) {
var _files = o.files;
if (_files === undefined)
return
if (_files.length === undefined)
_files = [_files];
files = files.concat(_files);
// set score for the word in each file to Scorer.term
for (j = 0; j < _files.length; j++) {
file = _files[j];
if (!(file in scoreMap))
scoreMap[file] = {}
scoreMap[file][word] = o.score;
}
});
// create the mapping
for (j = 0; j < files.length; j++) {
file = files[j];
if (file in fileMap)
fileMap[file].push(word);
else
fileMap[file] = [word];
}
}
// now check if the files don't contain excluded terms
for (file in fileMap) {
var valid = true;
// check if all requirements are matched
var filteredTermCount = // as search terms with length < 3 are discarded: ignore
searchterms.filter(function(term){return term.length > 2}).length
if (
fileMap[file].length != searchterms.length &&
fileMap[file].length != filteredTermCount
) continue;
// ensure that none of the excluded terms is in the search result
for (i = 0; i < excluded.length; i++) {
if (terms[excluded[i]] == file ||
titleterms[excluded[i]] == file ||
$u.contains(terms[excluded[i]] || [], file) ||
$u.contains(titleterms[excluded[i]] || [], file)) {
valid = false;
break;
}
}
// if we have still a valid result we can add it to the result list
if (valid) {
// select one (max) score for the file.
// for better ranking, we should calculate ranking by using words statistics like basic tf-idf...
var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]}));
results.push([docnames[file], titles[file], '', null, score, filenames[file]]);
}
}
return results;
},
/**
* helper function to return a node containing the
* search summary for a given text. keywords is a list
* of stemmed words, hlwords is the list of normal, unstemmed
* words. the first one is used to find the occurrence, the
* latter for highlighting it.
*/
makeSearchSummary : function(htmlText, keywords, hlwords) {
var text = Search.htmlToText(htmlText);
var textLower = text.toLowerCase();
var start = 0;
$.each(keywords, function() {
var i = textLower.indexOf(this.toLowerCase());
if (i > -1)
start = i;
});
start = Math.max(start - 120, 0);
var excerpt = ((start > 0) ? '...' : '') +
$.trim(text.substr(start, 240)) +
((start + 240 - text.length) ? '...' : '');
var rv = $('<div class="context"></div>').text(excerpt);
$.each(hlwords, function() {
rv = rv.highlightText(this, 'highlighted');
});
return rv;
}
};
$(document).ready(function() {
Search.init();
});

View file

@ -1,999 +0,0 @@
// Underscore.js 1.3.1
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function() {
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `global` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// Establish the object that gets returned to break out of a loop iteration.
var breaker = {};
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
var slice = ArrayProto.slice,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); };
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root['_'] = _;
}
// Current version.
_.VERSION = '1.3.1';
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
for (var key in obj) {
if (_.has(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
}
};
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
_.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
if (obj.length === +obj.length) results.length = obj.length;
return results;
};
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var reversed = _.toArray(obj).reverse();
if (context && !initial) iterator = _.bind(iterator, context);
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, iterator, context) {
var result;
any(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// Return all the elements that pass a truth test.
// Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
each(obj, function(value, index, list) {
if (!iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Determine whether all of the elements match a truth test.
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_.every = _.all = function(obj, iterator, context) {
var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
});
return result;
};
// Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
var any = _.some = _.any = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
if (result || (result = iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if a given value is included in the array or object using `===`.
// Aliased as `contains`.
_.include = _.contains = function(obj, target) {
var found = false;
if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
found = any(obj, function(value) {
return value === target;
});
return found;
};
// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
return _.map(obj, function(value) {
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
return _.map(obj, function(value){ return value[key]; });
};
// Return the maximum element or (element-based computation).
_.max = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return -Infinity;
var result = {computed : -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed >= result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return Infinity;
var result = {computed : Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Shuffle an array.
_.shuffle = function(obj) {
var shuffled = [], rand;
each(obj, function(value, index, list) {
if (index == 0) {
shuffled[0] = value;
} else {
rand = Math.floor(Math.random() * (index + 1));
shuffled[index] = shuffled[rand];
shuffled[rand] = value;
}
});
return shuffled;
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, iterator, context) {
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
criteria : iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = function(obj, val) {
var result = {};
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
each(obj, function(value, index) {
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
return result;
};
// Use a comparator function to figure out at what index an object should
// be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator) {
iterator || (iterator = _.identity);
var low = 0, high = array.length;
while (low < high) {
var mid = (low + high) >> 1;
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
}
return low;
};
// Safely convert anything iterable into a real, live array.
_.toArray = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
if (_.isArray(iterable)) return slice.call(iterable);
if (_.isArguments(iterable)) return slice.call(iterable);
return _.values(iterable);
};
// Return the number of elements in an object.
_.size = function(obj) {
return _.toArray(obj).length;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head`. The **guard** check allows it to work
// with `_.map`.
_.first = _.head = function(array, n, guard) {
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
// Returns everything but the last entry of the array. Especcialy useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_.initial = function(array, n, guard) {
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
if ((n != null) && !guard) {
return slice.call(array, Math.max(array.length - n, 0));
} else {
return array[array.length - 1];
}
};
// Returns everything but the first entry of the array. Aliased as `tail`.
// Especially useful on the arguments object. Passing an **index** will return
// the rest of the values in the array from that index onward. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = function(array, index, guard) {
return slice.call(array, (index == null) || guard ? 1 : index);
};
// Trim out all falsy values from an array.
_.compact = function(array) {
return _.filter(array, function(value){ return !!value; });
};
// Return a completely flattened version of an array.
_.flatten = function(array, shallow) {
return _.reduce(array, function(memo, value) {
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
memo[memo.length] = value;
return memo;
}, []);
};
// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator) {
var initial = iterator ? _.map(array, iterator) : array;
var result = [];
_.reduce(initial, function(memo, el, i) {
if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
memo[memo.length] = el;
result[result.length] = array[i];
}
return memo;
}, []);
return result;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(_.flatten(arguments, true));
};
// Produce an array that contains every item shared between all the
// passed-in arrays. (Aliased as "intersect" for back-compat.)
_.intersection = _.intersect = function(array) {
var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) {
return _.indexOf(other, item) >= 0;
});
});
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = _.flatten(slice.call(arguments, 1));
return _.filter(array, function(value){ return !_.include(rest, value); });
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function() {
var args = slice.call(arguments);
var length = _.max(_.pluck(args, 'length'));
var results = new Array(length);
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
return results;
};
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i, l;
if (isSorted) {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
return -1;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item) {
if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length;
while (i--) if (i in array && array[i] === item) return i;
return -1;
};
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
if (arguments.length <= 1) {
stop = start || 0;
start = 0;
}
step = arguments[2] || 1;
var len = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(len);
while(idx < len) {
range[idx++] = start;
start += step;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Reusable constructor function for prototype setting.
var ctor = function(){};
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Binding with arguments is also known as `curry`.
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
_.bind = function bind(func, context) {
var bound, args;
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_.bindAll = function(obj) {
var funcs = slice.call(arguments, 1);
if (funcs.length == 0) funcs = _.functions(obj);
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
};
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function(){ return func.apply(func, args); }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
// cleared.
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
_.throttle = function(func, wait) {
var context, args, timeout, throttling, more;
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
return function() {
context = this; args = arguments;
var later = function() {
timeout = null;
if (more) func.apply(context, args);
whenDone();
};
if (!timeout) timeout = setTimeout(later, wait);
if (throttling) {
more = true;
} else {
func.apply(context, args);
}
whenDone();
throttling = true;
};
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds.
_.debounce = function(func, wait) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
return memo = func.apply(this, arguments);
};
};
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
var args = [func].concat(slice.call(arguments, 0));
return wrapper.apply(this, args);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
};
// Returns a function that will only be executed after being called N times.
_.after = function(times, func) {
if (times <= 0) return func();
return function() {
if (--times < 1) { return func.apply(this, arguments); }
};
};
// Object Functions
// ----------------
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_.keys = nativeKeys || function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
return keys;
};
// Retrieve the values of an object's properties.
_.values = function(obj) {
return _.map(obj, _.identity);
};
// Return a sorted list of the function names available on the object.
// Aliased as `methods`
_.functions = _.methods = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (obj[prop] == null) obj[prop] = source[prop];
}
});
return obj;
};
// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
// Invokes interceptor with the obj, and then returns obj.
// The primary purpose of this method is to "tap into" a method chain, in
// order to perform operations on intermediate results within the chain.
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// Internal recursive comparison function.
function eq(a, b, stack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// Invoke a custom `isEqual` method if one is provided.
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
switch (className) {
// Strings, numbers, dates, and booleans are compared by value.
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
return a == String(b);
case '[object Number]':
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
// other numeric values.
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return +a == +b;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source == b.source &&
a.global == b.global &&
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase;
}
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = stack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (stack[length] == a) return true;
}
// Add the first object to the stack of traversed objects.
stack.push(a);
var size = 0, result = true;
// Recursively compare objects and arrays.
if (className == '[object Array]') {
// Compare array lengths to determine if a deep comparison is necessary.
size = a.length;
result = size == b.length;
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
// Ensure commutative equality for sparse arrays.
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
}
}
} else {
// Objects with different constructors are not equivalent.
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
// Deep compare objects.
for (var key in a) {
if (_.has(a, key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
}
}
// Ensure that both objects contain the same number of properties.
if (result) {
for (key in b) {
if (_.has(b, key) && !(size--)) break;
}
result = !size;
}
}
// Remove the first object from the stack of traversed objects.
stack.pop();
return result;
}
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
return eq(a, b, []);
};
// Is a given array, string, or object empty?
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false;
return true;
};
// Is a given value a DOM element?
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) == '[object Array]';
};
// Is a given variable an object?
_.isObject = function(obj) {
return obj === Object(obj);
};
// Is a given variable an arguments object?
_.isArguments = function(obj) {
return toString.call(obj) == '[object Arguments]';
};
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
return !!(obj && _.has(obj, 'callee'));
};
}
// Is a given value a function?
_.isFunction = function(obj) {
return toString.call(obj) == '[object Function]';
};
// Is a given value a string?
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
// Is a given value a number?
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
// Is the given value `NaN`?
_.isNaN = function(obj) {
// `NaN` is the only value for which `===` is not reflexive.
return obj !== obj;
};
// Is a given value a boolean?
_.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
// Is a given value a date?
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
// Is the given value a regular expression?
_.isRegExp = function(obj) {
return toString.call(obj) == '[object RegExp]';
};
// Is a given value equal to null?
_.isNull = function(obj) {
return obj === null;
};
// Is a given variable undefined?
_.isUndefined = function(obj) {
return obj === void 0;
};
// Has own property?
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
// Utility Functions
// -----------------
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
// previous owner. Returns a reference to the Underscore object.
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
// Keep the identity function around for default iterators.
_.identity = function(value) {
return value;
};
// Run a function **n** times.
_.times = function (n, iterator, context) {
for (var i = 0; i < n; i++) iterator.call(context, i);
};
// Escape a string for HTML interpolation.
_.escape = function(string) {
return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
};
// Add your own custom functions to the Underscore object, ensuring that
// they're correctly added to the OOP wrapper as well.
_.mixin = function(obj) {
each(_.functions(obj), function(name){
addToWrapper(name, _[name] = obj[name]);
});
};
// Generate a unique integer id (unique within the entire client session).
// Useful for temporary DOM ids.
var idCounter = 0;
_.uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
};
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /.^/;
// Within an interpolation, evaluation, or escaping, remove HTML escaping
// that had been previously added.
var unescape = function(code) {
return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(str, data) {
var c = _.templateSettings;
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
'with(obj||{}){__p.push(\'' +
str.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(c.escape || noMatch, function(match, code) {
return "',_.escape(" + unescape(code) + "),'";
})
.replace(c.interpolate || noMatch, function(match, code) {
return "'," + unescape(code) + ",'";
})
.replace(c.evaluate || noMatch, function(match, code) {
return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
})
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
+ "');}return __p.join('');";
var func = new Function('obj', '_', tmpl);
if (data) return func(data, _);
return function(data) {
return func.call(this, data, _);
};
};
// Add a "chain" function, which will delegate to the wrapper.
_.chain = function(obj) {
return _(obj).chain();
};
// The OOP Wrapper
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
var wrapper = function(obj) { this._wrapped = obj; };
// Expose `wrapper.prototype` as `_.prototype`
_.prototype = wrapper.prototype;
// Helper function to continue chaining intermediate results.
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
};
// A method to easily add functions to the OOP wrapper.
var addToWrapper = function(name, func) {
wrapper.prototype[name] = function() {
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
return result(func.apply(_, args), this._chain);
};
};
// Add all of the Underscore functions to the wrapper object.
_.mixin(_);
// Add all mutator Array functions to the wrapper.
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
var wrapped = this._wrapped;
method.apply(wrapped, arguments);
var length = wrapped.length;
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
return result(wrapped, this._chain);
};
});
// Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
});
// Start chaining a wrapped Underscore object.
wrapper.prototype.chain = function() {
this._chain = true;
return this;
};
// Extracts the result from a wrapped and chained object.
wrapper.prototype.value = function() {
return this._wrapped;
};
}).call(this);

View file

@ -1,31 +0,0 @@
// Underscore.js 1.3.1
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,
h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each=
b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===n)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===n)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(x&&a.map===x)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==
null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=
function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e=
e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})});
return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){f==0?b[0]=a:(d=Math.floor(Math.random()*(f+1)),b[f]=b[d],b[d]=a)});return b};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,
c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=
b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]);
return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,
d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(p&&a.indexOf===p)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(D&&a.lastIndexOf===D)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
var F=function(){};b.bind=function(a,c){var d,e;if(a.bind===s&&s)return s.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));F.prototype=a.prototype;var b=new F,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};
b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments,
1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};
b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};
b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.mixin=function(a){j(b.functions(a),
function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+
u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]=
function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=
true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,773 +0,0 @@
<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Index &mdash; Royalnet documentation</title>
<script type="text/javascript" src="_static/js/modernizr.min.js"></script>
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<script type="text/javascript" src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/rygdocs.css" type="text/css" />
<link rel="index" title="Index" href="#" />
<link rel="search" title="Search" href="search.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> Royalnet
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<ul>
<li class="toctree-l1"><a class="reference internal" href="runningroyalnet.html">Running Royalnet</a></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>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Royalnet</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html">Docs</a> &raquo;</li>
<li>Index</li>
<li class="wy-breadcrumbs-aside">
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<h1 id="index">Index</h1>
<div class="genindex-jumpbox">
<a href="#_"><strong>_</strong></a>
| <a href="#A"><strong>A</strong></a>
| <a href="#B"><strong>B</strong></a>
| <a href="#C"><strong>C</strong></a>
| <a href="#D"><strong>D</strong></a>
| <a href="#F"><strong>F</strong></a>
| <a href="#G"><strong>G</strong></a>
| <a href="#H"><strong>H</strong></a>
| <a href="#I"><strong>I</strong></a>
| <a href="#J"><strong>J</strong></a>
| <a href="#K"><strong>K</strong></a>
| <a href="#L"><strong>L</strong></a>
| <a href="#M"><strong>M</strong></a>
| <a href="#N"><strong>N</strong></a>
| <a href="#O"><strong>O</strong></a>
| <a href="#P"><strong>P</strong></a>
| <a href="#R"><strong>R</strong></a>
| <a href="#S"><strong>S</strong></a>
| <a href="#T"><strong>T</strong></a>
| <a href="#U"><strong>U</strong></a>
| <a href="#Y"><strong>Y</strong></a>
</div>
<h2 id="_">_</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.commands.CommandArgs.__getitem__">__getitem__() (royalnet.commands.CommandArgs method)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlInfo.__init__">__init__() (royalnet.audio.YtdlInfo method)</a>
<ul>
<li><a href="apireference.html#royalnet.database.Alchemy.__init__">(royalnet.database.Alchemy method)</a>
</li>
<li><a href="apireference.html#royalnet.network.Package.__init__">(royalnet.network.Package method)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.bots.DiscordBot._bot_factory">_bot_factory() (royalnet.bots.DiscordBot method)</a>
</li>
<li><a href="apireference.html#royalnet.database.Alchemy._create_tables">_create_tables() (royalnet.database.Alchemy method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.DiscordBot._data_factory">_data_factory() (royalnet.bots.DiscordBot method)</a>
<ul>
<li><a href="apireference.html#royalnet.bots.GenericBot._data_factory">(royalnet.bots.GenericBot method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.TelegramBot._data_factory">(royalnet.bots.TelegramBot method)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.audio.YtdlFile._default_ytdl_args">_default_ytdl_args (royalnet.audio.YtdlFile attribute)</a>
<ul>
<li><a href="apireference.html#royalnet.audio.YtdlInfo._default_ytdl_args">(royalnet.audio.YtdlInfo attribute)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.bots.TelegramBot._handle_callback_query">_handle_callback_query() (royalnet.bots.TelegramBot method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.TelegramBot._handle_message">_handle_message() (royalnet.bots.TelegramBot method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.TelegramBot._handle_update">_handle_update() (royalnet.bots.TelegramBot method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.bots.DiscordBot._init_client">_init_client() (royalnet.bots.DiscordBot method)</a>
<ul>
<li><a href="apireference.html#royalnet.bots.TelegramBot._init_client">(royalnet.bots.TelegramBot method)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.bots.GenericBot._init_commands">_init_commands() (royalnet.bots.GenericBot method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.GenericBot._init_database">_init_database() (royalnet.bots.GenericBot method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.GenericBot._init_loop">_init_loop() (royalnet.bots.GenericBot method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.GenericBot._init_network">_init_network() (royalnet.bots.GenericBot method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.GenericBot._init_sentry">_init_sentry() (royalnet.bots.GenericBot method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.DiscordBot._init_voice">_init_voice() (royalnet.bots.DiscordBot method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.DiscordBot._initialize">_initialize() (royalnet.bots.DiscordBot method)</a>
<ul>
<li><a href="apireference.html#royalnet.bots.GenericBot._initialize">(royalnet.bots.GenericBot method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.TelegramBot._initialize">(royalnet.bots.TelegramBot method)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.bots.DiscordBot._interface_factory">_interface_factory() (royalnet.bots.DiscordBot method)</a>
<ul>
<li><a href="apireference.html#royalnet.bots.GenericBot._interface_factory">(royalnet.bots.GenericBot method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.TelegramBot._interface_factory">(royalnet.bots.TelegramBot method)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.bots.GenericBot._network_handler">_network_handler() (royalnet.bots.GenericBot method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="A">A</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.bots.DiscordBot.add_to_music_data">add_to_music_data() (royalnet.bots.DiscordBot method)</a>
</li>
<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.Command.aliases">aliases (royalnet.commands.Command attribute)</a>
</li>
<li><a href="apireference.html#royalnet.utils.andformat">andformat() (in module royalnet.utils)</a>
</li>
<li><a href="apireference.html#royalnet.error.RoyalnetRequestError.args">args() (royalnet.error.RoyalnetRequestError property)</a>
</li>
<li><a href="apireference.html#royalnet.utils.asyncify">asyncify() (in module royalnet.utils)</a>
</li>
</ul></td>
</tr></table>
<h2 id="B">B</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.commands.CommandInterface.bot">bot (royalnet.commands.CommandInterface attribute)</a>
</li>
</ul></td>
</tr></table>
<h2 id="C">C</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.utils.cdj">cdj() (in module royalnet.utils)</a>
</li>
<li><a href="apireference.html#royalnet.commands.Command">Command (class in royalnet.commands)</a>
</li>
<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>
</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.YtdlMp3.create_and_ready_from_url">create_and_ready_from_url() (royalnet.audio.YtdlMp3 class method)</a>
</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>
<ul>
<li><a href="apireference.html#royalnet.audio.YtdlMp3.create_from_url">(royalnet.audio.YtdlMp3 class method)</a>
</li>
</ul></li>
</ul></td>
</tr></table>
<h2 id="D">D</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.database.DatabaseConfig">DatabaseConfig (class in royalnet.database)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlDiscord.delete">delete() (royalnet.audio.YtdlDiscord method)</a>
<ul>
<li><a href="apireference.html#royalnet.audio.YtdlFile.delete">(royalnet.audio.YtdlFile method)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlMp3.delete">(royalnet.audio.YtdlMp3 method)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.commands.CommandData.delete_invoking">delete_invoking() (royalnet.commands.CommandData method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.commands.Command.description">description (royalnet.commands.Command attribute)</a>
</li>
<li><a href="apireference.html#royalnet.utils.discord_escape">discord_escape() (in module royalnet.utils)</a>
</li>
<li><a href="apireference.html#royalnet.bots.DiscordBot">DiscordBot (class in royalnet.bots)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlFile.download_file">download_file() (royalnet.audio.YtdlFile method)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlFile.download_from_url">download_from_url() (royalnet.audio.YtdlFile class method)</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.network.NetworkServer.find_client">find_client() (royalnet.network.NetworkServer method)</a>
</li>
<li><a href="apireference.html#royalnet.network.NetworkServer.find_destination">find_destination() (royalnet.network.NetworkServer method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.network.Package.from_dict">from_dict() (royalnet.network.Package static method)</a>
<ul>
<li><a href="apireference.html#royalnet.network.Request.from_dict">(royalnet.network.Request static method)</a>
</li>
<li><a href="apireference.html#royalnet.network.Response.from_dict">(royalnet.network.Response class method)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.network.Package.from_json_bytes">from_json_bytes() (royalnet.network.Package static method)</a>
</li>
<li><a href="apireference.html#royalnet.network.Package.from_json_string">from_json_string() (royalnet.network.Package static method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="G">G</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.bots.GenericBot">GenericBot (class in royalnet.bots)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.commands.CommandData.get_author">get_author() (royalnet.commands.CommandData method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.GenericBot.get_secret">get_secret() (royalnet.bots.GenericBot method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="H">H</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.audio.YtdlFile.has_info">has_info() (royalnet.audio.YtdlFile method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="I">I</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.network.NetworkLink.identify">identify() (royalnet.network.NetworkLink method)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlDiscord.info">info() (royalnet.audio.YtdlDiscord property)</a>
<ul>
<li><a href="apireference.html#royalnet.audio.YtdlMp3.info">(royalnet.audio.YtdlMp3 property)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.bots.DiscordBot.interface_name">interface_name (royalnet.bots.DiscordBot attribute)</a>
<ul>
<li><a href="apireference.html#royalnet.bots.GenericBot.interface_name">(royalnet.bots.GenericBot attribute)</a>
</li>
<li><a href="apireference.html#royalnet.bots.TelegramBot.interface_name">(royalnet.bots.TelegramBot attribute)</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<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>
<li><a href="apireference.html#royalnet.audio.FileAudioSource.is_opus">is_opus() (royalnet.audio.FileAudioSource method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="J">J</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.commands.CommandArgs.joined">joined() (royalnet.commands.CommandArgs method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="K">K</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.commands.CommandData.keyboard">keyboard() (royalnet.commands.CommandData method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.commands.KeyboardExpiredError">KeyboardExpiredError</a>
</li>
</ul></td>
</tr></table>
<h2 id="L">L</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.network.NetworkServer.listener">listener() (royalnet.network.NetworkServer method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.commands.CommandInterface.loop">loop (royalnet.commands.CommandInterface attribute)</a>
</li>
</ul></td>
</tr></table>
<h2 id="M">M</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.commands.CommandArgs.match">match() (royalnet.commands.CommandArgs method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.utils.NetworkHandler.message_type">message_type (royalnet.utils.NetworkHandler attribute)</a>
</li>
</ul></td>
</tr></table>
<h2 id="N">N</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.commands.Command.name">name (royalnet.commands.Command attribute)</a>
<ul>
<li><a href="apireference.html#royalnet.commands.CommandInterface.name">(royalnet.commands.CommandInterface attribute)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.commands.CommandInterface.net_request">net_request() (royalnet.commands.CommandInterface method)</a>
</li>
<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>
</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.network.NotConnectedError">NotConnectedError</a>
</li>
<li><a href="apireference.html#royalnet.network.NotIdentifiedError">NotIdentifiedError</a>
</li>
<li><a href="apireference.html#royalnet.utils.numberemojiformat">numberemojiformat() (in module royalnet.utils)</a>
</li>
</ul></td>
</tr></table>
<h2 id="O">O</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.audio.YtdlFile.open">open() (royalnet.audio.YtdlFile method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.commands.CommandArgs.optional">optional() (royalnet.commands.CommandArgs method)</a>
</li>
<li><a href="apireference.html#royalnet.utils.ordinalformat">ordinalformat() (in module royalnet.utils)</a>
</li>
</ul></td>
</tr></table>
<h2 id="P">P</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.network.Package">Package (class in royalnet.network)</a>
</li>
<li><a href="apireference.html#royalnet.utils.parse_5etools_entry">parse_5etools_entry() (in module royalnet.utils)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlDiscord.pcm_available">pcm_available() (royalnet.audio.YtdlDiscord method)</a>
<ul>
<li><a href="apireference.html#royalnet.audio.YtdlMp3.pcm_available">(royalnet.audio.YtdlMp3 method)</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.utils.plusformat">plusformat() (in module royalnet.utils)</a>, <a href="apireference.html#royalnet.utils.plusformat">[1]</a>
</li>
<li><a href="apireference.html#royalnet.commands.CommandInterface.prefix">prefix (royalnet.commands.CommandInterface attribute)</a>
</li>
</ul></td>
</tr></table>
<h2 id="R">R</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.network.Response.raise_on_error">raise_on_error() (royalnet.network.Response method)</a>
<ul>
<li><a href="apireference.html#royalnet.network.ResponseError.raise_on_error">(royalnet.network.ResponseError method)</a>
</li>
<li><a href="apireference.html#royalnet.network.ResponseSuccess.raise_on_error">(royalnet.network.ResponseSuccess method)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.audio.FileAudioSource.read">read() (royalnet.audio.FileAudioSource method)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlDiscord.ready_up">ready_up() (royalnet.audio.YtdlDiscord method)</a>
<ul>
<li><a href="apireference.html#royalnet.audio.YtdlMp3.ready_up">(royalnet.audio.YtdlMp3 method)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.network.NetworkLink.receive">receive() (royalnet.network.NetworkLink method)</a>
</li>
<li><a href="apireference.html#royalnet.commands.CommandInterface.register_keyboard_key">register_keyboard_key() (royalnet.commands.CommandInterface method)</a>
</li>
<li><a href="apireference.html#royalnet.commands.CommandInterface.register_net_handler">register_net_handler() (royalnet.commands.CommandInterface method)</a>
</li>
<li><a href="apireference.html#royalnet.database.relationshiplinkchain">relationshiplinkchain() (in module royalnet.database)</a>
</li>
<li><a href="apireference.html#royalnet.commands.CommandData.reply">reply() (royalnet.commands.CommandData method)</a>
<ul>
<li><a href="apireference.html#royalnet.network.Package.reply">(royalnet.network.Package method)</a>
</li>
</ul></li>
<li><a href="apireference.html#royalnet.network.Request">Request (class in royalnet.network)</a>
</li>
<li><a href="apireference.html#royalnet.network.NetworkLink.request">request() (royalnet.network.NetworkLink method)</a>
</li>
<li><a href="apireference.html#royalnet.commands.Command.require_alchemy_tables">require_alchemy_tables (royalnet.commands.Command attribute)</a>
</li>
<li><a href="apireference.html#royalnet.network.Response">Response (class in royalnet.network)</a>
</li>
<li><a href="apireference.html#royalnet.network.ResponseError">ResponseError (class in royalnet.network)</a>
</li>
<li><a href="apireference.html#royalnet.network.ResponseSuccess">ResponseSuccess (class in royalnet.network)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlInfo.retrieve_for_url">retrieve_for_url() (royalnet.audio.YtdlInfo class method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.network.NetworkServer.route_package">route_package() (royalnet.network.NetworkServer method)</a>
</li>
<li><a href="apireference.html#module-royalnet.audio">royalnet.audio (module)</a>
</li>
<li><a href="apireference.html#module-royalnet.bots">royalnet.bots (module)</a>
</li>
<li><a href="apireference.html#module-royalnet.commands">royalnet.commands (module)</a>
</li>
<li><a href="apireference.html#module-royalnet.database">royalnet.database (module)</a>
</li>
<li><a href="apireference.html#module-royalnet.error">royalnet.error (module)</a>
</li>
<li><a href="apireference.html#module-royalnet.network">royalnet.network (module)</a>
</li>
<li><a href="apireference.html#module-royalnet.utils">royalnet.utils (module)</a>
</li>
<li><a href="apireference.html#module-royalnet.web">royalnet.web (module)</a>
</li>
<li><a href="apireference.html#royalnet.error.RoyalnetRequestError">RoyalnetRequestError</a>
</li>
<li><a href="apireference.html#royalnet.error.RoyalnetResponseError">RoyalnetResponseError</a>
</li>
<li><a href="apireference.html#royalnet.web.Royalprint">Royalprint (class in royalnet.web)</a>
</li>
<li><a href="apireference.html#royalnet.bots.DiscordBot.run">run() (royalnet.bots.DiscordBot method)</a>
<ul>
<li><a href="apireference.html#royalnet.bots.GenericBot.run">(royalnet.bots.GenericBot method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.TelegramBot.run">(royalnet.bots.TelegramBot method)</a>
</li>
<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>
</ul></li>
<li><a href="apireference.html#royalnet.bots.GenericBot.run_blocking">run_blocking() (royalnet.bots.GenericBot method)</a>
<ul>
<li><a href="apireference.html#royalnet.network.NetworkServer.run_blocking">(royalnet.network.NetworkServer method)</a>
</li>
</ul></li>
</ul></td>
</tr></table>
<h2 id="S">S</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.bots.TelegramBot.safe_api_call">safe_api_call() (royalnet.bots.TelegramBot static method)</a>
</li>
<li><a href="apireference.html#royalnet.utils.safeformat">safeformat() (in module royalnet.utils)</a>
</li>
<li><a href="apireference.html#royalnet.network.NetworkLink.send">send() (royalnet.network.NetworkLink method)</a>
</li>
<li><a href="apireference.html#royalnet.network.NetworkServer.serve">serve() (royalnet.network.NetworkServer method)</a>
</li>
<li><a href="apireference.html#royalnet.database.Alchemy.session_acm">session_acm() (royalnet.database.Alchemy method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.database.Alchemy.session_cm">session_cm() (royalnet.database.Alchemy method)</a>
</li>
<li><a href="apireference.html#royalnet.bots.GenericBot.set_secret">set_secret() (royalnet.bots.GenericBot method)</a>
</li>
<li><a href="apireference.html#royalnet.utils.sleep_until">sleep_until() (in module royalnet.utils)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlDiscord.spawn_audiosource">spawn_audiosource() (royalnet.audio.YtdlDiscord method)</a>
</li>
<li><a href="apireference.html#royalnet.utils.splitstring">splitstring() (in module royalnet.utils)</a>
</li>
<li><a href="apireference.html#royalnet.commands.Command.syntax">syntax (royalnet.commands.Command attribute)</a>
</li>
</ul></td>
</tr></table>
<h2 id="T">T</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.utils.telegram_escape">telegram_escape() (in module royalnet.utils)</a>
</li>
<li><a href="apireference.html#royalnet.bots.TelegramBot">TelegramBot (class in royalnet.bots)</a>
</li>
<li><a href="apireference.html#royalnet.network.Package.to_dict">to_dict() (royalnet.network.Package method)</a>
<ul>
<li><a href="apireference.html#royalnet.network.Request.to_dict">(royalnet.network.Request method)</a>
</li>
<li><a href="apireference.html#royalnet.network.Response.to_dict">(royalnet.network.Response method)</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.audio.YtdlInfo.to_discord_embed">to_discord_embed() (royalnet.audio.YtdlInfo method)</a>
</li>
<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>
</ul></td>
</tr></table>
<h2 id="U">U</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<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>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<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>
<li><a href="apireference.html#royalnet.audio.YtdlFile.update_info">update_info() (royalnet.audio.YtdlFile method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="Y">Y</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.utils.ytdldateformat">ytdldateformat() (in module royalnet.utils)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlDiscord">YtdlDiscord (class in royalnet.audio)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="apireference.html#royalnet.audio.YtdlFile">YtdlFile (class in royalnet.audio)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlInfo">YtdlInfo (class in royalnet.audio)</a>
</li>
<li><a href="apireference.html#royalnet.audio.YtdlMp3">YtdlMp3 (class in royalnet.audio)</a>
</li>
</ul></td>
</tr></table>
</div>
</div>
<footer>
<hr/>
<div role="contentinfo">
<p>
&copy; Copyright 2019, Stefano Pigozzi
</p>
</div>
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/rtfd/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script type="text/javascript">
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>

View file

@ -1,254 +0,0 @@
<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>royalnet &mdash; Royalnet documentation</title>
<script type="text/javascript" src="_static/js/modernizr.min.js"></script>
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<script type="text/javascript" src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/rygdocs.css" type="text/css" />
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Running Royalnet" href="runningroyalnet.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="#" class="icon icon-home"> Royalnet
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<ul>
<li class="toctree-l1"><a class="reference internal" href="runningroyalnet.html">Running Royalnet</a></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>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="#">Royalnet</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="#">Docs</a> &raquo;</li>
<li>royalnet</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/index.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<div class="section" id="royalnet">
<h1>royalnet<a class="headerlink" href="#royalnet" title="Permalink to this headline"></a></h1>
<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><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>
<li class="toctree-l3"><a class="reference internal" href="creatingacommand.html#direct-access">Direct access</a></li>
<li class="toctree-l3"><a class="reference internal" href="creatingacommand.html#optional-access">Optional access</a></li>
<li class="toctree-l3"><a class="reference internal" href="creatingacommand.html#full-string">Full string</a></li>
<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>
<li class="toctree-l2"><a class="reference internal" href="creatingacommand.html#using-the-database">Using the database</a><ul>
<li class="toctree-l3"><a class="reference internal" href="creatingacommand.html#querying-the-database">Querying the database</a></li>
<li class="toctree-l3"><a class="reference internal" href="creatingacommand.html#adding-filters-to-the-query">Adding filters to the query</a></li>
<li class="toctree-l3"><a class="reference internal" href="creatingacommand.html#ordering-the-results-of-a-query">Ordering the results of a query</a></li>
<li class="toctree-l3"><a class="reference internal" href="creatingacommand.html#fetching-the-results-of-a-query">Fetching the results of a query</a></li>
<li class="toctree-l3"><a class="reference internal" href="creatingacommand.html#more-alchemy">More Alchemy</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="creatingacommand.html#comunicating-via-royalnet">Comunicating via Royalnet</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="apireference.html">API Reference</a><ul>
<li class="toctree-l2"><a class="reference internal" href="apireference.html#module-royalnet.audio">Audio</a></li>
<li class="toctree-l2"><a class="reference internal" href="apireference.html#module-royalnet.bots">Bots</a></li>
<li class="toctree-l2"><a class="reference internal" href="apireference.html#module-royalnet.commands">Commands</a></li>
<li class="toctree-l2"><a class="reference internal" href="apireference.html#module-royalnet.database">Database</a></li>
<li class="toctree-l2"><a class="reference internal" href="apireference.html#module-royalnet.network">Network</a></li>
<li class="toctree-l2"><a class="reference internal" href="apireference.html#module-royalnet.utils">Utils</a></li>
<li class="toctree-l2"><a class="reference internal" href="apireference.html#module-royalnet.web">Web</a></li>
<li class="toctree-l2"><a class="reference internal" href="apireference.html#module-royalnet.error">Error</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="some-useful-links">
<h2>Some useful links<a class="headerlink" href="#some-useful-links" title="Permalink to this headline"></a></h2>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/royal-games/royalnet">Royalnet on GitHub</a></p></li>
<li><p><a class="reference internal" href="genindex.html"><span class="std std-ref">Index</span></a></p></li>
</ul>
</div>
</div>
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
<a href="runningroyalnet.html" class="btn btn-neutral float-right" title="Running Royalnet" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right"></span></a>
</div>
<hr/>
<div role="contentinfo">
<p>
&copy; Copyright 2019, Stefano Pigozzi
</p>
</div>
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/rtfd/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script type="text/javascript">
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>

Binary file not shown.

View file

@ -1,248 +0,0 @@
<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Python Module Index &mdash; Royalnet documentation</title>
<script type="text/javascript" src="_static/js/modernizr.min.js"></script>
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<script type="text/javascript" src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/rygdocs.css" type="text/css" />
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> Royalnet
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<ul>
<li class="toctree-l1"><a class="reference internal" href="runningroyalnet.html">Running Royalnet</a></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>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Royalnet</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html">Docs</a> &raquo;</li>
<li>Python Module Index</li>
<li class="wy-breadcrumbs-aside">
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<h1>Python Module Index</h1>
<div class="modindex-jumpbox">
<a href="#cap-r"><strong>r</strong></a>
</div>
<table class="indextable modindextable">
<tr class="pcap"><td></td><td>&#160;</td><td></td></tr>
<tr class="cap" id="cap-r"><td></td><td>
<strong>r</strong></td><td></td></tr>
<tr>
<td><img src="_static/minus.png" class="toggler"
id="toggle-1" style="display: none" alt="-" /></td>
<td>
<code class="xref">royalnet</code></td><td>
<em></em></td></tr>
<tr class="cg-1">
<td></td>
<td>&#160;&#160;&#160;
<a href="apireference.html#module-royalnet.audio"><code class="xref">royalnet.audio</code></a></td><td>
<em></em></td></tr>
<tr class="cg-1">
<td></td>
<td>&#160;&#160;&#160;
<a href="apireference.html#module-royalnet.bots"><code class="xref">royalnet.bots</code></a></td><td>
<em></em></td></tr>
<tr class="cg-1">
<td></td>
<td>&#160;&#160;&#160;
<a href="apireference.html#module-royalnet.commands"><code class="xref">royalnet.commands</code></a></td><td>
<em></em></td></tr>
<tr class="cg-1">
<td></td>
<td>&#160;&#160;&#160;
<a href="apireference.html#module-royalnet.database"><code class="xref">royalnet.database</code></a></td><td>
<em></em></td></tr>
<tr class="cg-1">
<td></td>
<td>&#160;&#160;&#160;
<a href="apireference.html#module-royalnet.error"><code class="xref">royalnet.error</code></a></td><td>
<em></em></td></tr>
<tr class="cg-1">
<td></td>
<td>&#160;&#160;&#160;
<a href="apireference.html#module-royalnet.network"><code class="xref">royalnet.network</code></a></td><td>
<em></em></td></tr>
<tr class="cg-1">
<td></td>
<td>&#160;&#160;&#160;
<a href="apireference.html#module-royalnet.utils"><code class="xref">royalnet.utils</code></a></td><td>
<em></em></td></tr>
<tr class="cg-1">
<td></td>
<td>&#160;&#160;&#160;
<a href="apireference.html#module-royalnet.web"><code class="xref">royalnet.web</code></a></td><td>
<em></em></td></tr>
</table>
</div>
</div>
<footer>
<hr/>
<div role="contentinfo">
<p>
&copy; Copyright 2019, Stefano Pigozzi
</p>
</div>
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/rtfd/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script type="text/javascript">
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>

View file

@ -1,271 +0,0 @@
<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Running Royalnet &mdash; Royalnet documentation</title>
<script type="text/javascript" src="_static/js/modernizr.min.js"></script>
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<script type="text/javascript" src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/rygdocs.css" type="text/css" />
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Royalnet Commands" href="creatingacommand.html" />
<link rel="prev" title="royalnet" href="index.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> Royalnet
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<ul class="current">
<li class="toctree-l1 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>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Royalnet</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html">Docs</a> &raquo;</li>
<li>Running Royalnet</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/runningroyalnet.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<div class="section" id="running-royalnet">
<h1>Running Royalnet<a class="headerlink" href="#running-royalnet" title="Permalink to this headline"></a></h1>
<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>, youll have to setup the system keyring.</p>
<p>On Windows and desktop Linux, this is already configured;
on a headless Linux instance, youll 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>Youll 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 youll 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>Youll 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 youre 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 youll be asked for a Telegram Bot API token.
You can get one from <a class="reference external" href="https://t.me/BotFather">&#64;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, youll be asked for a Sentry DSN. You probably wont 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, youre 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>You can run the main <code class="docutils literal notranslate"><span class="pre">royalnet</span></code> process by running:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">python3</span><span class="o">.</span><span class="mi">7</span> <span class="o">-</span><span class="n">m</span> <span class="n">royalnet</span>
</pre></div>
</div>
<p>To see all available options, you can run:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">python3</span><span class="o">.</span><span class="mi">7</span> <span class="o">-</span><span class="n">m</span> <span class="n">royalnet</span> <span class="o">--</span><span class="n">help</span>
</pre></div>
</div>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>All royalnet options should be specified <strong>after</strong> the word <code class="docutils literal notranslate"><span class="pre">royalnet</span></code>, or else they will be passed to
the Python interpreter.</p>
</div>
</div>
</div>
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
<a href="creatingacommand.html" class="btn btn-neutral float-right" title="Royalnet Commands" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right"></span></a>
<a href="index.html" class="btn btn-neutral float-left" title="royalnet" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left"></span> Previous</a>
</div>
<hr/>
<div role="contentinfo">
<p>
&copy; Copyright 2019, Stefano Pigozzi
</p>
</div>
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/rtfd/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script type="text/javascript">
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>

View file

@ -1,209 +0,0 @@
<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Search &mdash; Royalnet documentation</title>
<script type="text/javascript" src="_static/js/modernizr.min.js"></script>
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<script type="text/javascript" src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/searchtools.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/rygdocs.css" type="text/css" />
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="#" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> Royalnet
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="#" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<ul>
<li class="toctree-l1"><a class="reference internal" href="runningroyalnet.html">Running Royalnet</a></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>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Royalnet</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html">Docs</a> &raquo;</li>
<li>Search</li>
<li class="wy-breadcrumbs-aside">
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<noscript>
<div id="fallback" class="admonition warning">
<p class="last">
Please activate JavaScript to enable the search
functionality.
</p>
</div>
</noscript>
<div id="search-results">
</div>
</div>
</div>
<footer>
<hr/>
<div role="contentinfo">
<p>
&copy; Copyright 2019, Stefano Pigozzi
</p>
</div>
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/rtfd/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script type="text/javascript">
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
<script type="text/javascript">
jQuery(function() { Search.loadIndex("searchindex.js"); });
</script>
<script type="text/javascript" id="searchindexloader"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

View file

@ -5,66 +5,7 @@ These pages were automatically generated from docstrings in code.
They might be outdated, or incomplete. They might be outdated, or incomplete.
Audio .. automodule:: royalnet
------------------------------------
.. automodule:: royalnet.audio
:members:
:undoc-members:
:private-members:
Bots
------------------------------------
.. automodule:: royalnet.bots
:members:
:undoc-members:
:private-members:
Commands
------------------------------------
.. automodule:: royalnet.commands
:members:
:undoc-members:
:private-members:
Database
------------------------------------
.. automodule:: royalnet.database
:members:
:undoc-members:
:private-members:
Network
------------------------------------
.. automodule:: royalnet.network
:members:
:undoc-members:
:private-members:
Utils
------------------------------------
.. automodule:: royalnet.utils
:members:
:undoc-members:
:private-members:
Web
------------------------------------
.. automodule:: royalnet.web
:members:
:undoc-members:
:private-members:
Error
------------------------------------
.. automodule:: royalnet.error
:members: :members:
:undoc-members: :undoc-members:
:private-members: :private-members:

View file

@ -1,24 +1,44 @@
bcrypt>=3.1.7 aiohttp==3.5.4
git+https://github.com/Rapptz/discord.py@master#egg=discord.py[voice] async-timeout==3.0.1
python-telegram-bot>=11.1.0 attrs==19.3.0
websockets>=6.0 bcrypt==3.1.7
pytest>=4.3.1 certifi==2019.9.11
psycopg2-binary>=2.8 cffi==1.13.0
aiohttp>=3.5.4 chardet==3.0.4
sqlalchemy>=1.3.2 Click==7.0
Markdown>=3.1 cryptography==2.8
dateparser>=0.7.1 dateparser==0.7.2
youtube_dl>=2019.7.30 discord.py==1.2.4
ffmpeg-python>=0.1.17 dnspython==1.15.0
Sphinx>=2.0.1 dnspython3==1.15.0
sphinx_rtd_theme>=0.4.3 entrypoints==0.3
PyNaCl>=1.3.0 ffmpeg==1.4
werkzeug>=0.15.4 Flask==1.1.1
flask>=1.0.3 future==0.18.1
markdown2>=2.3.8 idna==2.8
mcstatus>=2.2.1 itsdangerous==1.1.0
sortedcontainers>=2.1.0 Jinja2==2.10.3
sentry-sdk>=0.11.1 keyring==19.2.0
click>=7.0 markdown2==2.3.8
keyring>=19.2.0 MarkupSafe==1.1.1
urllib3>=1.25.6 mcstatus==2.2.1
multidict==4.5.2
psycopg2-binary==2.8.3
pycparser==2.19
python-dateutil==2.8.0
python-telegram-bot==12.2.0
pytz==2019.3
pywin32-ctypes==0.2.0
regex==2019.8.19
royalherald==5.0b8
sentry-sdk==0.13.0
six==1.12.0
sortedcontainers==2.1.0
SQLAlchemy==1.3.10
tornado==6.0.3
tzlocal==2.0.0
urllib3==1.25.6
websockets==8.0.2
Werkzeug==0.16.0
yarl==1.3.0
youtube-dl==2019.10.16

View file

@ -1,4 +1,3 @@
from . import audio, bots, database, network, utils, error, web, version from . import audio, bots, commands, packs, database, utils, error, web, version
from royalnet import commands
__all__ = ["audio", "bots", "commands", "database", "network", "utils", "error", "web", "version"] __all__ = ["audio", "bots", "commands", "database", "utils", "error", "web", "version"]

View file

@ -2,6 +2,7 @@ import click
import typing import typing
import importlib import importlib
import royalnet as r import royalnet as r
import royalherald as rh
import multiprocessing import multiprocessing
import keyring import keyring
@ -13,8 +14,8 @@ import keyring
help="Enable/disable the Discord module.") help="Enable/disable the Discord module.")
@click.option("-d", "--database", type=str, default=None, @click.option("-d", "--database", type=str, default=None,
help="The PostgreSQL database path.") help="The PostgreSQL database path.")
@click.option("-c", "--command-packs", type=str, multiple=True, default=[], @click.option("-p", "--packs", type=str, multiple=True, default=[],
help="The names of the command pack modules that should be imported.") help="The names of the Packs that should be used.")
@click.option("-n", "--network-address", type=str, default=None, @click.option("-n", "--network-address", type=str, default=None,
help="The Network server URL to connect to.") help="The Network server URL to connect to.")
@click.option("-l", "--local-network-server", is_flag=True, default=False, @click.option("-l", "--local-network-server", is_flag=True, default=False,
@ -26,7 +27,7 @@ import keyring
def run(telegram: typing.Optional[bool], def run(telegram: typing.Optional[bool],
discord: typing.Optional[bool], discord: typing.Optional[bool],
database: typing.Optional[str], database: typing.Optional[str],
command_packs: typing.List[str], packs: typing.Tuple[str],
network_address: typing.Optional[str], network_address: typing.Optional[str],
local_network_server: bool, local_network_server: bool,
secrets_name: str, secrets_name: str,
@ -63,41 +64,39 @@ def run(telegram: typing.Optional[bool],
# Start the network server # Start the network server
if local_network_server: if local_network_server:
server_process = multiprocessing.Process(name="Network Server", server_process = multiprocessing.Process(name="Network Server",
target=r.network.NetworkServer("0.0.0.0", target=rh.Server("0.0.0.0", 44444, network_password).run_blocking,
44444,
network_password).run_blocking,
daemon=True) daemon=True)
server_process.start() server_process.start()
network_address = "ws://127.0.0.1:44444/" network_address = "ws://127.0.0.1:44444/"
# Create a Royalnet configuration # Create a Royalnet configuration
network_config: typing.Optional[r.network.NetworkConfig] = None network_config: typing.Optional[rh.Config] = None
if network_address is not None: if network_address is not None:
network_config = r.network.NetworkConfig(network_address, network_password) network_config = rh.Config(network_address, network_password)
# Create a Alchemy configuration # Create a Alchemy configuration
telegram_db_config: typing.Optional[r.database.DatabaseConfig] = None telegram_db_config: typing.Optional[r.database.DatabaseConfig] = None
discord_db_config: typing.Optional[r.database.DatabaseConfig] = None discord_db_config: typing.Optional[r.database.DatabaseConfig] = None
if database is not None: if database is not None:
telegram_db_config = r.database.DatabaseConfig(database, telegram_db_config = r.database.DatabaseConfig(database,
r.database.tables.User, r.packs.common.tables.User,
r.database.tables.Telegram, r.packs.common.tables.Telegram,
"tg_id") "tg_id")
discord_db_config = r.database.DatabaseConfig(database, discord_db_config = r.database.DatabaseConfig(database,
r.database.tables.User, r.packs.common.tables.User,
r.database.tables.Discord, r.packs.common.tables.Discord,
"discord_id") "discord_id")
# Import command packs # Import command packs
if not command_packs: packs: typing.List[str] = list(packs)
raise click.ClickException("No command packs were specified.") packs.append("royalnet.packs.common") # common pack is always imported
enabled_commands = [] enabled_commands = []
for pack in command_packs: for pack in packs:
imported = importlib.import_module(pack) imported = importlib.import_module(pack)
try: try:
imported_commands = imported.commands imported_commands = imported.available_commands
except AttributeError: except AttributeError:
raise click.ClickException(f"{pack} isn't a Royalnet command pack.") raise click.ClickException(f"{pack} isn't a Royalnet Pack.")
enabled_commands = [*enabled_commands, *imported_commands] enabled_commands = [*enabled_commands, *imported_commands]
telegram_process: typing.Optional[multiprocessing.Process] = None telegram_process: typing.Optional[multiprocessing.Process] = None

View file

@ -52,7 +52,7 @@ class PlayMode:
def queue_preview(self) -> typing.List[YtdlDiscord]: def queue_preview(self) -> typing.List[YtdlDiscord]:
"""Display all the videos in the PlayMode as a list, if possible. """Display all the videos in the PlayMode as a list, if possible.
To be used with ``queue`` commands, for example. To be used with ``queue`` packs, for example.
Raises: Raises:
NotImplementedError: If a preview can't be generated. NotImplementedError: If a preview can't be generated.

View file

@ -1,4 +1,4 @@
"""Various bot interfaces, and a generic class to create new ones.""" """Various bot interfaces, and a common class to create new ones."""
from .generic import GenericBot from .generic import GenericBot
from .telegram import TelegramBot from .telegram import TelegramBot

View file

@ -45,7 +45,7 @@ class DiscordBot(GenericBot):
# noinspection PyMethodParameters,PyAbstractClass # noinspection PyMethodParameters,PyAbstractClass
class DiscordData(CommandData): class DiscordData(CommandData):
def __init__(data, interface: CommandInterface, message: discord.Message): def __init__(data, interface: CommandInterface, message: discord.Message):
data._interface = interface super().__init__(interface)
data.message = message data.message = message
async def reply(data, text: str): async def reply(data, text: str):
@ -145,6 +145,12 @@ class DiscordBot(GenericBot):
error_message += '\n'.join(e.args) error_message += '\n'.join(e.args)
await data.reply(error_message) await data.reply(error_message)
async def on_connect(cli):
log.debug("Connected to Discord")
async def on_disconnect(cli):
log.error("Disconnected from Discord!")
async def on_ready(cli) -> None: async def on_ready(cli) -> None:
log.debug("Connection successful, client is ready") log.debug("Connection successful, client is ready")
await cli.change_presence(status=discord.Status.online) await cli.change_presence(status=discord.Status.online)
@ -209,7 +215,6 @@ class DiscordBot(GenericBot):
await self.client.login(token) await self.client.login(token)
log.info(f"Connecting to Discord") log.info(f"Connecting to Discord")
await self.client.connect() await self.client.connect()
# TODO: how to stop?
async def add_to_music_data(self, dfiles: typing.List[YtdlDiscord], guild: discord.Guild): async def add_to_music_data(self, dfiles: typing.List[YtdlDiscord], guild: discord.Guild):
"""Add a list of :py:class:`royalnet.audio.YtdlDiscord` to the corresponding music_data object.""" """Add a list of :py:class:`royalnet.audio.YtdlDiscord` to the corresponding music_data object."""

View file

@ -3,11 +3,11 @@ import asyncio
import logging import logging
import sentry_sdk import sentry_sdk
import keyring import keyring
import royalherald as rh
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
from sentry_sdk.integrations.aiohttp import AioHttpIntegration from sentry_sdk.integrations.aiohttp import AioHttpIntegration
from sentry_sdk.integrations.logging import LoggingIntegration from sentry_sdk.integrations.logging import LoggingIntegration
from ..utils import * from ..utils import *
from ..network import *
from ..database import * from ..database import *
from ..commands import * from ..commands import *
from ..error import * from ..error import *
@ -17,24 +17,28 @@ log = logging.getLogger(__name__)
class GenericBot: class GenericBot:
"""A generic bot class, to be used as base for the other more specific classes, such as """A common bot class, to be used as base for the other more specific classes, such as
:py:class:`royalnet.bots.TelegramBot` and :py:class:`royalnet.bots.DiscordBot`. """ :py:class:`royalnet.bots.TelegramBot` and :py:class:`royalnet.bots.DiscordBot`. """
interface_name = NotImplemented interface_name = NotImplemented
def _init_commands(self) -> None: def _init_commands(self) -> None:
"""Generate the ``commands`` dictionary required to handle incoming messages, and the ``network_handlers`` """Generate the ``packs`` dictionary required to handle incoming messages, and the ``network_handlers``
dictionary required to handle incoming requests. """ dictionary required to handle incoming requests. """
log.info(f"Registering commands...") log.info(f"Registering packs...")
self._Interface = self._interface_factory() self._Interface = self._interface_factory()
self._Data = self._data_factory() self._Data = self._data_factory()
self.commands = {} self.commands = {}
self.network_handlers: typing.Dict[str, typing.Type[NetworkHandler]] = {} self.network_handlers: typing.Dict[str, typing.Callable[["GenericBot", typing.Any],
typing.Awaitable[typing.Dict]]] = {}
for SelectedCommand in self.uninitialized_commands: for SelectedCommand in self.uninitialized_commands:
interface = self._Interface() interface = self._Interface()
try: try:
command = SelectedCommand(interface) command = SelectedCommand(interface)
except Exception as e: except Exception as e:
log.error(f"{e.__class__.__qualname__} during the registration of {SelectedCommand.__qualname__}") log.error(f"{e.__class__.__qualname__} during the registration of {SelectedCommand.__qualname__}")
continue
# Linking the command to the interface
interface.command = command
# Override the main command name, but warn if it's overriding something # Override the main command name, but warn if it's overriding something
if f"{interface.prefix}{SelectedCommand.name}" in self.commands: if f"{interface.prefix}{SelectedCommand.name}" in self.commands:
log.warning(f"Overriding (already defined): {SelectedCommand.__qualname__} -> {interface.prefix}{SelectedCommand.name}") log.warning(f"Overriding (already defined): {SelectedCommand.__qualname__} -> {interface.prefix}{SelectedCommand.name}")
@ -56,26 +60,27 @@ class GenericBot:
bot = self bot = self
loop = self.loop loop = self.loop
def register_net_handler(ci, message_type: str, network_handler: typing.Callable): def register_herald_action(ci,
self.network_handlers[message_type] = network_handler event_name: str,
coroutine: typing.Callable[[typing.Any], typing.Awaitable[typing.Dict]]) -> None:
self.network_handlers[event_name] = coroutine
def unregister_net_handler(ci, message_type: str): def unregister_herald_action(ci, event_name: str):
del self.network_handlers[message_type] del self.network_handlers[event_name]
async def net_request(ci, request: Request, destination: str) -> dict: async def call_herald_action(ci, destination: str, event_name: str, args: typing.Dict) -> typing.Dict:
if self.network is None: if self.network is None:
raise InvalidConfigError("Royalnet is not enabled on this bot") raise UnsupportedError("Herald is not enabled on this bot")
response_dict: dict = await self.network.request(request.to_dict(), destination) request: rh.Request = rh.Request(handler=event_name, data=args)
if "type" not in response_dict: response: rh.Response = await self.network.request(destination=destination, request=request)
raise RoyalnetResponseError("Response is missing a type") if isinstance(response, rh.ResponseFailure):
elif response_dict["type"] == "ResponseSuccess": raise CommandError(f"Herald action call failed:\n"
response: typing.Union[ResponseSuccess, ResponseError] = ResponseSuccess.from_dict(response_dict) f"[p]{response}[/p]")
elif response_dict["type"] == "ResponseError": elif isinstance(response, rh.ResponseSuccess):
response = ResponseError.from_dict(response_dict)
else:
raise RoyalnetResponseError("Response type is unknown")
response.raise_on_error()
return response.data return response.data
else:
raise TypeError(f"Other Herald Link returned unknown response:\n"
f"[p]{response}[/p]")
return GenericInterface return GenericInterface
@ -83,56 +88,44 @@ class GenericBot:
raise NotImplementedError() raise NotImplementedError()
def _init_network(self): def _init_network(self):
"""Create a :py:class:`royalnet.network.NetworkLink`, and run it as a :py:class:`asyncio.Task`.""" """Create a :py:class:`royalherald.Link`, and run it as a :py:class:`asyncio.Task`."""
if self.uninitialized_network_config is not None: if self.uninitialized_network_config is not None:
self.network: NetworkLink = NetworkLink(self.uninitialized_network_config.master_uri, self.network: rh.Link = rh.Link(self.uninitialized_network_config.master_uri,
self.uninitialized_network_config.master_secret, self.uninitialized_network_config.master_secret,
self.interface_name, self._network_handler) self.interface_name,
self._network_handler)
log.debug(f"Running NetworkLink {self.network}") log.debug(f"Running NetworkLink {self.network}")
self.loop.create_task(self.network.run()) self.loop.create_task(self.network.run())
async def _network_handler(self, request_dict: dict) -> dict: async def _network_handler(self, request: rh.Request) -> rh.Response:
"""Handle a single :py:class:`dict` received from the :py:class:`royalnet.network.NetworkLink`.
Returns:
Another :py:class:`dict`, formatted as a :py:class:`royalnet.network.Response`."""
# Convert the dict to a Request
try:
request: Request = Request.from_dict(request_dict)
except TypeError:
log.warning(f"Invalid request received: {request_dict}")
return ResponseError("invalid_request",
f"The Request that you sent was invalid. Check extra_info to see what you sent.",
extra_info={"you_sent": request_dict}).to_dict()
log.debug(f"Received {request} from the NetworkLink")
try: try:
network_handler = self.network_handlers[request.handler] network_handler = self.network_handlers[request.handler]
except KeyError: except KeyError:
log.warning(f"Missing network_handler for {request.handler}") log.warning(f"Missing network_handler for {request.handler}")
return ResponseError("no_handler", f"This Link is missing a network handler for {request.handler}.").to_dict() return rh.ResponseFailure("no_handler", f"This bot is missing a network handler for {request.handler}.")
try: else:
log.debug(f"Using {network_handler} as handler for {request.handler}") log.debug(f"Using {network_handler} as handler for {request.handler}")
response: Response = await getattr(network_handler, self.interface_name)(self, request.data) try:
return response.to_dict() response_data = await network_handler(self, **request.data)
return rh.ResponseSuccess(data=response_data)
except Exception as e: except Exception as e:
sentry_sdk.capture_exception(e) sentry_sdk.capture_exception(e)
log.debug(f"Exception {e} in {network_handler}") log.error(f"Exception {e} in {network_handler}")
return ResponseError("exception_in_handler", return rh.ResponseFailure("exception_in_handler",
f"An exception was raised in {network_handler} for {request.handler}. Check " f"An exception was raised in {network_handler} for {request.handler}.",
f"extra_info for details.",
extra_info={ extra_info={
"type": e.__class__.__name__, "type": e.__class__.__name__,
"str": str(e) "message": str(e)
}).to_dict() })
def _init_database(self): def _init_database(self):
"""Create an :py:class:`royalnet.database.Alchemy` with the tables required by the commands. Then, """Create an :py:class:`royalnet.database.Alchemy` with the tables required by the packs. Then,
find the chain that links the ``master_table`` to the ``identity_table``. """ find the chain that links the ``master_table`` to the ``identity_table``. """
if self.uninitialized_database_config: if self.uninitialized_database_config:
log.info(f"Database: enabled") log.info(f"Database: enabled")
required_tables = {self.uninitialized_database_config.master_table, self.uninitialized_database_config.identity_table} required_tables = {self.uninitialized_database_config.master_table, self.uninitialized_database_config.identity_table}
for command in self.uninitialized_commands: for command in self.uninitialized_commands:
required_tables = required_tables.union(command.require_alchemy_tables) required_tables = required_tables.union(command.tables)
log.debug(f"Required tables: {', '.join([item.__qualname__ for item in required_tables])}") log.debug(f"Required tables: {', '.join([item.__qualname__ for item in required_tables])}")
self.alchemy = Alchemy(self.uninitialized_database_config.database_uri, 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.master_table = self.alchemy.__getattribute__(self.uninitialized_database_config.master_table.__name__)
@ -168,7 +161,7 @@ class GenericBot:
self.loop = self.uninitialized_loop self.loop = self.uninitialized_loop
def __init__(self, *, def __init__(self, *,
network_config: typing.Optional[NetworkConfig] = None, network_config: typing.Optional[rh.Config] = None,
database_config: typing.Optional[DatabaseConfig] = None, database_config: typing.Optional[DatabaseConfig] = None,
commands: typing.List[typing.Type[Command]] = None, commands: typing.List[typing.Type[Command]] = None,
sentry_dsn: typing.Optional[str] = None, sentry_dsn: typing.Optional[str] = None,
@ -198,7 +191,7 @@ class GenericBot:
self.initialized = True self.initialized = True
def run(self): def run(self):
"""A blocking coroutine that should make the bot start listening to commands and requests.""" """A blocking coroutine that should make the bot start listening to packs and requests."""
raise NotImplementedError() raise NotImplementedError()
def run_blocking(self, verbose=False): def run_blocking(self, verbose=False):

View file

@ -5,6 +5,7 @@ import urllib3
import asyncio import asyncio
import sentry_sdk import sentry_sdk
import logging as _logging import logging as _logging
import warnings
from .generic import GenericBot from .generic import GenericBot
from ..utils import * from ..utils import *
from ..error import * from ..error import *
@ -54,7 +55,7 @@ class TelegramBot(GenericBot):
# noinspection PyMethodParameters,PyAbstractClass # noinspection PyMethodParameters,PyAbstractClass
class TelegramData(CommandData): class TelegramData(CommandData):
def __init__(data, interface: CommandInterface, update: telegram.Update): def __init__(data, interface: CommandInterface, update: telegram.Update):
data.interface = interface super().__init__(interface)
data.update = update data.update = update
async def reply(data, text: str): async def reply(data, text: str):
@ -74,7 +75,7 @@ class TelegramBot(GenericBot):
if error_if_none: if error_if_none:
raise CommandError("No command caller for this message") raise CommandError("No command caller for this message")
return None return None
query = data.interface.session.query(self.master_table) query = data.session.query(self.master_table)
for link in self.identity_chain: for link in self.identity_chain:
query = query.join(link.mapper.class_) query = query.join(link.mapper.class_)
query = query.filter(self.identity_column == user.id) query = query.filter(self.identity_column == user.id)
@ -84,11 +85,12 @@ class TelegramBot(GenericBot):
return result return result
async def keyboard(data, text: str, keyboard: typing.Dict[str, typing.Callable]) -> None: async def keyboard(data, text: str, keyboard: typing.Dict[str, typing.Callable]) -> None:
warnings.warn("keyboard is deprecated, please avoid using it", category=DeprecationWarning)
tg_keyboard = [] tg_keyboard = []
for key in keyboard: for key in keyboard:
press_id = uuid.uuid4() press_id = uuid.uuid4()
tg_keyboard.append([telegram.InlineKeyboardButton(key, callback_data=str(press_id))]) tg_keyboard.append([telegram.InlineKeyboardButton(key, callback_data=str(press_id))])
data.interface.register_keyboard_key(key_name=str(press_id), callback=keyboard[key]) data._interface.register_keyboard_key(key_name=str(press_id), callback=keyboard[key])
await TelegramBot.safe_api_call(data.update.effective_chat.send_message, await TelegramBot.safe_api_call(data.update.effective_chat.send_message,
telegram_escape(text), telegram_escape(text),
reply_markup=telegram.InlineKeyboardMarkup(tg_keyboard), reply_markup=telegram.InlineKeyboardMarkup(tg_keyboard),
@ -107,8 +109,7 @@ class TelegramBot(GenericBot):
try: try:
return await asyncify(f, *args, **kwargs) return await asyncify(f, *args, **kwargs)
except telegram.error.TimedOut as error: except telegram.error.TimedOut as error:
log.debug(f"Timed out during {f.__qualname__} (retrying in 15s): {error}") log.debug(f"Timed out during {f.__qualname__} (retrying immediatly): {error}")
await asyncio.sleep(15)
continue continue
except telegram.error.NetworkError as error: except telegram.error.NetworkError as error:
log.debug(f"Network error during {f.__qualname__} (skipping): {error}") log.debug(f"Network error during {f.__qualname__} (skipping): {error}")
@ -177,6 +178,8 @@ class TelegramBot(GenericBot):
error_message = f"🦀 [b]{e.__class__.__name__}[/b] 🦀\n" error_message = f"🦀 [b]{e.__class__.__name__}[/b] 🦀\n"
error_message += '\n'.join(e.args) error_message += '\n'.join(e.args)
await data.reply(error_message) await data.reply(error_message)
if __debug__:
raise
async def _handle_callback_query(self, update: telegram.Update): async def _handle_callback_query(self, update: telegram.Update):
query: telegram.CallbackQuery = update.callback_query query: telegram.CallbackQuery = update.callback_query
@ -202,6 +205,9 @@ class TelegramBot(GenericBot):
error_text = f"⛔️ {e.__class__.__name__}\n" error_text = f"⛔️ {e.__class__.__name__}\n"
error_text += '\n'.join(e.args) error_text += '\n'.join(e.args)
await self.safe_api_call(query.answer, text=error_text) await self.safe_api_call(query.answer, text=error_text)
if __debug__:
raise
else:
return return
else: else:
await self.safe_api_call(query.answer, text=response) await self.safe_api_call(query.answer, text=response)

View file

@ -20,7 +20,7 @@ class Command:
"""The syntax of the command, to be displayed when a :py:exc:`royalnet.error.InvalidInputError` is raised, """The syntax of the command, to be displayed when a :py:exc:`royalnet.error.InvalidInputError` is raised,
in the format ``(required_arg) [optional_arg]``.""" in the format ``(required_arg) [optional_arg]``."""
require_alchemy_tables: typing.Set = set() tables: typing.Set = set()
"""A set of :py:class:`royalnet.database` tables that must exist for this command to work.""" """A set of :py:class:`royalnet.database` tables that must exist for this command to work."""
def __init__(self, interface: CommandInterface): def __init__(self, interface: CommandInterface):

View file

@ -1,8 +1,17 @@
import typing import typing
import warnings
from .commanderrors import UnsupportedError from .commanderrors import UnsupportedError
from .commandinterface import CommandInterface
class CommandData: class CommandData:
def __init__(self, interface: CommandInterface):
self._interface: CommandInterface = interface
if len(self._interface.command.tables) > 0:
self.session = self._interface.alchemy.Session()
else:
self.session = None
async def reply(self, text: str) -> None: async def reply(self, text: str) -> None:
"""Send a text message to the channel where the call was made. """Send a text message to the channel where the call was made.
@ -22,6 +31,7 @@ class CommandData:
"""Send a keyboard having the keys of the dict as keys and calling the correspondent values on a press. """Send a keyboard having the keys of the dict as keys and calling the correspondent values on a press.
The function should be passed the :py:class:`CommandData` instance as a argument.""" The function should be passed the :py:class:`CommandData` instance as a argument."""
warnings.warn("keyboard is deprecated, please avoid using it", category=DeprecationWarning)
raise UnsupportedError("'keyboard' is not supported on this platform") raise UnsupportedError("'keyboard' is not supported on this platform")
async def delete_invoking(self, error_if_unavailable=False) -> None: async def delete_invoking(self, error_if_unavailable=False) -> None:

View file

@ -1,7 +1,9 @@
import typing import typing
import asyncio import asyncio
import royalherald as rh
from .commanderrors import UnsupportedError from .commanderrors import UnsupportedError
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from .command import Command
from ..database import Alchemy from ..database import Alchemy
from ..bots import GenericBot from ..bots import GenericBot
@ -14,30 +16,21 @@ class CommandInterface:
loop: asyncio.AbstractEventLoop = NotImplemented loop: asyncio.AbstractEventLoop = NotImplemented
def __init__(self): def __init__(self):
if self.alchemy: self.command: typing.Optional[Command] = None # Will be bound after the command has been created
self.session = self.alchemy.Session()
else:
self.session = None
def register_net_handler(self, message_type: str, network_handler: typing.Callable): def register_herald_action(self,
"""Register a new handler for messages received through Royalnet.""" event_name: str,
raise UnsupportedError("'register_net_handler' is not supported on this platform") coroutine: typing.Callable[[typing.Any], typing.Awaitable[typing.Dict]]):
raise UnsupportedError(f"{self.register_herald_action.__name__} is not supported on this platform")
def unregister_net_handler(self, message_type: str): def unregister_herald_action(self, event_name: str):
"""Remove a Royalnet handler.""" raise UnsupportedError(f"{self.unregister_herald_action.__name__} is not supported on this platform")
raise UnsupportedError("'unregister_net_handler' is not supported on this platform")
async def net_request(self, message, destination: str) -> dict: async def call_herald_action(self, destination: str, event_name: str, args: typing.Dict) -> typing.Dict:
"""Send data through a :py:class:`royalnet.network.NetworkLink` and wait for a raise UnsupportedError(f"{self.call_herald_action.__name__} is not supported on this platform")
:py:class:`royalnet.network.Reply`.
Parameters:
message: The data to be sent. Must be :py:mod:`pickle`-able.
destination: The destination of the request, either in UUID format or node name."""
raise UnsupportedError("'net_request' is not supported on this platform")
def register_keyboard_key(self, key_name: str, callback: typing.Callable): def register_keyboard_key(self, key_name: str, callback: typing.Callable):
raise UnsupportedError("'register_keyboard_key' is not supported on this platform") raise UnsupportedError(f"{self.register_keyboard_key.__name__} is not supported on this platform")
def unregister_keyboard_key(self, key_name: str): def unregister_keyboard_key(self, key_name: str):
raise UnsupportedError("'unregister_keyboard_key' is not supported on this platform") raise UnsupportedError(f"{self.unregister_keyboard_key.__name__} is not supported on this platform")

View file

@ -1,13 +0,0 @@
from .debug_error import DebugErrorCommand
from .debug_keyboard import DebugKeyboardCommand
from .debug_invoking import DebugInvokingCommand
commands = [
DebugErrorCommand,
DebugKeyboardCommand,
DebugInvokingCommand,
]
__all__ = [command.__name__ for command in commands]

View file

@ -1,13 +0,0 @@
import typing
from ..command import Command
from ..commandargs import CommandArgs
from ..commanddata import CommandData
class DebugErrorCommand(Command):
name: str = "debug_error"
description: str = "Causa un'eccezione nel bot."
async def run(self, args: CommandArgs, data: CommandData) -> None:
raise Exception("debug_error command was called")

View file

@ -1,14 +0,0 @@
import typing
from ..command import Command
from ..commandargs import CommandArgs
from ..commanddata import CommandData
class DebugInvokingCommand(Command):
name: str = "debug_invoking"
description: str = "Elimina il messaggio di invocazione."
async def run(self, args: CommandArgs, data: CommandData) -> None:
await data.delete_invoking(error_if_unavailable=True)
await data.reply("🗑 Messaggio eliminato.")

View file

@ -1,18 +0,0 @@
import typing
from ..command import Command
from ..commandargs import CommandArgs
from ..commanddata import CommandData
class DebugKeyboardCommand(Command):
name: str = "debug_keyboard"
description: str = "Invia una tastiera di prova."
async def _callback(self, data: CommandData):
await data.reply("OK.")
async def run(self, args: CommandArgs, data: CommandData) -> None:
await data.keyboard("This is a keyboard.", {
"✅ OK": self._callback
})

View file

@ -1,40 +0,0 @@
"""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 .ciaoruozi import CiaoruoziCommand
from .color import ColorCommand
from .cv import CvCommand
from .diario import DiarioCommand
from .ping import PingCommand
from .rage import RageCommand
from .reminder import ReminderCommand
from .ship import ShipCommand
from .smecds import SmecdsCommand
from .videochannel import VideochannelCommand
from .dnditem import DnditemCommand
from .dndspell import DndspellCommand
from .trivia import TriviaCommand
from .mm import MmCommand
commands = [
CiaoruoziCommand,
ColorCommand,
CvCommand,
DiarioCommand,
PingCommand,
RageCommand,
ReminderCommand,
ShipCommand,
SmecdsCommand,
VideochannelCommand,
DnditemCommand,
DndspellCommand,
TriviaCommand,
MmCommand,
]
__all__ = [command.__name__ for command in commands]

View file

@ -1,13 +0,0 @@
import typing
from ..command import Command
from ..commandargs import CommandArgs
from ..commanddata import CommandData
class PingCommand(Command):
name: str = "ping"
description: str = "Gioca a ping-pong con il bot."
async def run(self, args: CommandArgs, data: CommandData) -> None:
await data.reply("🏓 Pong!")

View file

@ -1,30 +0,0 @@
"""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
from .youtube import YoutubeCommand
from .soundcloud import SoundcloudCommand
from .zawarudo import ZawarudoCommand
commands = [
PauseCommand,
PlayCommand,
PlaymodeCommand,
QueueCommand,
SkipCommand,
SummonCommand,
YoutubeCommand,
SoundcloudCommand,
ZawarudoCommand
]
__all__ = [command.__name__ for command in commands]

View file

@ -3,6 +3,5 @@
from .alchemy import Alchemy from .alchemy import Alchemy
from .relationshiplinkchain import relationshiplinkchain from .relationshiplinkchain import relationshiplinkchain
from .databaseconfig import DatabaseConfig from .databaseconfig import DatabaseConfig
from . import tables
__all__ = ["Alchemy", "relationshiplinkchain", "DatabaseConfig", "tables"] __all__ = ["Alchemy", "relationshiplinkchain", "DatabaseConfig"]

View file

@ -1,22 +0,0 @@
from .users import User
from .telegram import Telegram
from .diario import Diario
from .aliases import Alias
from .activekvgroups import ActiveKvGroup
from .keyvalues import Keyvalue
from .keygroups import Keygroup
from .discord import Discord
from .wikipages import WikiPage
from .wikirevisions import WikiRevision
from .medals import Medal
from .medalawards import MedalAward
from .bios import Bio
from .reminders import Reminder
from .triviascores import TriviaScore
from .mmdecisions import MMDecision
from .mmevents import MMEvent
from .mmresponse import MMResponse
__all__ = ["User", "Telegram", "Diario", "Alias", "ActiveKvGroup", "Keyvalue", "Keygroup", "Discord", "WikiPage",
"WikiRevision", "Medal", "MedalAward", "Bio", "Reminder", "TriviaScore", "MMDecision", "MMEvent",
"MMResponse"]

View file

@ -1,39 +0,0 @@
from sqlalchemy import Column, \
Integer, \
DateTime, \
ForeignKey
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import relationship
from .users import User
from .medals import Medal
class MedalAward:
__tablename__ = "MedalAward"
@declared_attr
def award_id(self):
return Column(Integer, primary_key=True)
@declared_attr
def date(self):
return Column(DateTime)
@declared_attr
def medal_id(self):
return Column(Integer, ForeignKey("medals.mid"), nullable=False)
@declared_attr
def royal_id(self):
return Column(Integer, ForeignKey("users.uid"), nullable=False)
@declared_attr
def medal(self):
return relationship("Medal", backref="awarded_to")
@declared_attr
def royal(self):
return relationship("User", backref="medals_received")
def __repr__(self):
return f"<MedalAward of {self.medal} to {self.royal} on {self.date}>"

View file

@ -1,40 +0,0 @@
from sqlalchemy import Column, \
Integer, \
String
from sqlalchemy.ext.declarative import declared_attr
# noinspection PyUnresolvedReferences
from .keygroups import Keygroup
class Medal:
__tablename__ = "medals"
@declared_attr
def mid(self):
return Column(Integer, primary_key=True)
@declared_attr
def name(self):
return Column(String, nullable=False)
@declared_attr
def description(self):
return Column(String)
@declared_attr
def icon(self):
return Column(String)
@declared_attr
def classes(self):
return Column(String, nullable=False, default="")
@declared_attr
def score(self):
return Column(Integer, nullable=False)
def __str__(self):
return self.name
def __repr__(self):
return f"<Medal {self.mid}: {self.name}>"

View file

@ -1,14 +1,13 @@
import typing import typing
if typing.TYPE_CHECKING: import royalherald as rh
from .network import ResponseError
class RoyalnetRequestError(Exception): class RoyalnetRequestError(Exception):
"""An error was raised while handling the Royalnet request. """An error was raised while handling the Royalnet request.
This exception contains the :py:class:`royalnet.network.ResponseError` that was returned by the other Link.""" This exception contains the :py:class:`royalherald.ResponseFailure` that was returned by the other Link."""
def __init__(self, error: "ResponseError"): def __init__(self, error: rh.ResponseFailure):
self.error: "ResponseError" = error self.error: rh.ResponseFailure = error
@property @property
def args(self): def args(self):
@ -16,4 +15,4 @@ class RoyalnetRequestError(Exception):
class RoyalnetResponseError(Exception): class RoyalnetResponseError(Exception):
"""The :py:class:`royalnet.network.Response` that was received is invalid.""" """The :py:class:`royalherald.Response` that was received is invalid."""

View file

@ -1,20 +0,0 @@
"""Royalnet (websocket) related classes."""
from .request import Request
from .response import Response, ResponseSuccess, ResponseError
from .package import Package
from .networklink import NetworkLink, NetworkError, NotConnectedError, NotIdentifiedError, ConnectionClosedError
from .networkserver import NetworkServer
from .networkconfig import NetworkConfig
__all__ = ["NetworkLink",
"NetworkError",
"NotConnectedError",
"NotIdentifiedError",
"Package",
"NetworkServer",
"NetworkConfig",
"ConnectionClosedError",
"Request",
"Response",
"ResponseSuccess",
"ResponseError"]

View file

@ -1,9 +0,0 @@
class NetworkConfig:
def __init__(self,
master_uri: str,
master_secret: str):
if not (master_uri.startswith("ws://")
or master_uri.startswith("wss://")):
raise ValueError("Invalid protocol (must be ws:// or wss://)")
self.master_uri = master_uri
self.master_secret = master_secret

Some files were not shown because too many files have changed in this diff Show more