diff --git a/master/objects.inv b/master/objects.inv index 7d74898..ccf4127 100644 Binary files a/master/objects.inv and b/master/objects.inv differ diff --git a/master/reference/attachment/index.html b/master/reference/attachment/index.html index 2a43d23..220489b 100644 --- a/master/reference/attachment/index.html +++ b/master/reference/attachment/index.html @@ -4044,6 +4044,39 @@

+ +

Raises:

+ + + + + + + + + + + + + + + + + +
TypeDescription
+ RuntimeError + +
+

If the magic library is not installed.

+
+
+ TypeError + +
+

If the file is not a string, BytesIO, or Path object.

+
+
+ diff --git a/master/reference/commands/index.html b/master/reference/commands/index.html index 3b085bf..0dfcf77 100644 --- a/master/reference/commands/index.html +++ b/master/reference/commands/index.html @@ -618,15 +618,34 @@ + + @@ -1105,15 +1138,34 @@ + + @@ -1300,6 +1366,54 @@

Commands

Using commands and events is the main way to interact with the bot.

+

Command argument detection

+

One of the most powerful features of NioBot is the command argument interpretation system. +When you create a niobot command, the arguments are automatically detected, and their desired +type is inferred from the type hints in the function signature.

+

This means that foo: str will always give you a string, bar: int will try to give you an integer, +or throw an error if it cannot convert the user-given argument.

+

As of v1.2.0, you can take advantage of the keyword-only and positional args in Python. +Normally, when you specify a function like async def mycommand(ctx, x: str), niobot will see +that you want an argument, x, and will do just that. It will take the user's input, and give you +the value for x. However, if the user specifies multiple words for x, it will only give the first one +to the function, unless the user warps the argument in "quotes".

+

import niobot
+bot = niobot.NioBot()
+
+@bot.command()
+async def mycommand(ctx, x: str):
+    await ctx.respond(f"Your argument was: {x}")
+
+If you ran !mycommand hello world, the bot would respond with Your argument was: hello.

+

With keyword-only arguments, you can make use of "greedy" arguments. +While you could previously do this by manually constructing the niobot.Argument type, +you can now do this with the * syntax in Python.

+

import niobot
+bot = niobot.NioBot()
+
+@bot.command()
+async def mycommand(ctx, *, x: str):
+    await ctx.respond(f"Your argument was: {x}")
+
+If you ran !mycommand hello world, the bot would respond with Your argument was: hello world.

+

And, as for positional args, if you want to fetch a set of arguments, you can do so by specifying +*args. This will give you a tuple containing every whitespace-delimited argument after the command.

+

import niobot
+bot = niobot.NioBot()
+
+@bot.command()
+async def mycommand(ctx, *args: str):
+    await ctx.respond(f"Your arguments were: {args}")
+
+If you ran !mycommand hello world, the bot would respond with Your arguments were: ('hello', 'world').

+
+

Position & KW-Only args are final and strings!

+

If you specify a keyword or positional argument, you cannot have any arguments afterwards. +Furthermore, (currently) both of these arguments are always strings. Trying to specify +another type will throw an error.

+
+
+

Reference

@@ -1893,6 +2007,31 @@

+

+ parse_args + + + + async + + +

+
parse_args(
+    ctx: Context,
+) -> Dict[Argument, Union[Any, List[Any]]]
+
+ +
+ +

Parses the arguments for the current command.

+ +
+ +
+ +
+ +

invoke @@ -2553,7 +2692,7 @@

- October 9, 2023 20:31:51 + November 21, 2024 17:01:40 diff --git a/master/search/search_index.json b/master/search/search_index.json index 1316efd..69d6235 100644 --- a/master/search/search_index.json +++ b/master/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Index","text":"

Welcome to the Nio-Bot Documentation

"},{"location":"#what-is-niobot","title":"What is NioBot?","text":"

NioBot (& nio-bot) is a framework built upon the matrix-nio client library, built with the creation of bots in mind. NioBot provides a simple and intuitive way to build bots for the Matrix framework by simplifying a lot of the mundane tasks that come with building a bot.

By making use of nio, you can access the full power of the Matrix protocol, while still being able to build a bot using nio-bot with ease.

Why NioBot, and not just regular nio?

Take for example, the following code:

from niobot import NioBot, Context\n\n\nbot = NioBot(\n    homeserver=\"https://matrix-client.matrix.org\",\n    user_id=\"@my_user:matrix.org\",\n    command_prefix=\"!\"\n)\n\n@bot.command(\"ping\")\nasync def ping_command(ctx: Context):\n    \"\"\"Checks if the bot is online & responding.\"\"\"\n    await ctx.respond(\"Pong!\")\n\n\nbot.run(access_token=\"abc123\")\n

This is an incredibly simple working example of a bot that responds to the command !ping with Pong!. You simply run this with python3 bot.py, and you're away!

Here's the same code (not fully, there are a LOT of behind the scenes in niobot) in base nio:

import asyncio\nfrom nio import AsyncClient, MatrixRoom, RoomMessage\n\n\nclient = AsyncClient(\"https://matrix-client.matrix.org\", \"@my_user:matrix.org\")\n\n\nasync def ping_command(room: MatrixRoom, event: RoomMessage):\n    if event.sender == client.user_id:\n        return\n    body = event.body\n    if event.body.startswith(\"!\"):\n        if event.body.split()[0][1:].lower() == \"ping\":\n            await client.room_send(\n                room.room_id,\n                \"m.room.message\",\n                {\n                    \"msgtype\": \"m.notice\",\n                    \"body\": \"Pong!\",\n                    \"m.relates_to\": {\n                        \"m.in_reply_to\": {\n                            \"event_id\": event.event_id\n                        }\n                    }\n                }\n            )\n\n\nclient.add_event_callback(ping_command, (RoomMessage,))\nclient.access_token = \"abc123\"\n\nasyncio.run(client.sync_forever())\n

At first, it doesn't look too difficult right? But, as you start to add more commands, or add more functionality, you'll end up building up more and more boilerplate, and it can get quite messy quite quickly.

This is where nio-bot comes in. By abstracting away a lot of the nuanced functionality in favour of a simplified interface, so that you can focus on building meaningful code, rather than annoying boilerplate.

"},{"location":"#features","title":"Features","text":"

NioBot contains all of the features of matrix-nio, plus a few more:

  • Integrated command system: Easily create commands with a decorater and function. That's all you need.
  • Powerful context: Access all of the metadata you need about a command with the [niobot.Context][] object, given to every command.
  • Robust error handling: Your bot won't crash as soon as an error occurs. Instead, it will be passed on to a handler, and the bot will continue running.
  • Simple configuration: Pass in a few parameters to [niobot.NioBot][], and you're ready to go.
  • Automatic Markdown + HTML rendering: Send messages in markdown or HTML, and NioBot will automatically render them for you.
  • Easy events: Listening for events in NioBot is incredibly similar to listening for commands, and is just as easy.
NioBot does not make an effort to support end-to-end encryption

While NioBot does support end-to-end encryption, it does not make an effort to support it. Making end-to-end encryption work in a headless fashion is incredibly difficult, and while it does work, users have reported that E2EE is unreliable and can unexpectedly break at any time, and is hard to debug.

We do not recommend that you expect E2EE to work when building a bot with NioBot. If it works, that's great! If not, we cannot help.

"},{"location":"#installation","title":"Installation","text":"

You can install NioBot from PyPi:

pip install nio-bot\n# Or, get the latest pre-release\npip install --pre nio-bot\n

or straight from git:

pip install git+https://github.com/nexy7574/nio-bot.git\n
"},{"location":"#contact","title":"Contact","text":"

See the Matrix room for help, or open a GitHub issue for bugs or feature requests.

"},{"location":"changelog/","title":"Changelog","text":""},{"location":"changelog/#unreleased","title":"Unreleased","text":"These changes are not in a stable release.

You can only get these changes by installing the library from GitHub. This is not recommended in production, as these changes are often not properly battle-tested.

However, if you encounter an issue with these changes, you should open an issue on GitHub so that we can release them sooner!

  • Remove force_write properly
  • Fix <instance> has no attribute 'mro' error when initialising auto-detected arguments
  • Fixed niocli get-access-token crashing on windows
  • Fixed NioBot throwing a warning about failing key uploads without logging the actual error
  • Allowed command_prefix to be an iterable, converting single-strings into a single-item iterable too.
  • Changed the event type of the message event to be any nio.RoomMessage, not just Text.
  • Merged xyzamorganblurhash into ImageAttachment
  • Removed deprecated automatic_markdown_parser option and functionality in NioBot
  • Fixed niobot.utils.parsers.EventParser raising an error when used
  • Added beautifulsoup4 as a hard dependency. Backwards compatibility is still kept in case bs4 is not installed.
  • Fixed some typing dotted around the client
  • Removed the deprecated name parameter from niobot checks
  • Added support for passing raw nio.Event types to event listeners
  • Added proper support for typing.Optional in automatic argument detection
  • Added support for *args in automatic argument detection
  • Fixed niobot attachments (Image/Video/Audio) sending null for metadata, which may cause incorrect client behaviours
  • Added niobot.utils.Mentions to handle intentional mentions in messages
  • (Typing) send_message can now reply to any RoomMessage, not just RoomMessageText.
  • niobot.util.help_command.help_command_callback was removed, in line with deprecation.
  • niobot.NioBot.start will now query /_matrix/client/versions to fetch server version metadata.
  • Fix RuntimeError due to concurrent typing in send_message
  • Updated the documentation index page and added documentation for Mentions
  • Fixed the versioned docs deployment
  • Updated the help command
    • Unified all of the functions into a single class, niobot.utils.help_command.DefaultHelpCommand, to make subclassing easier.
    • default_help_command was replaced with DefaultHelpCommand().respond.
    • Help command will no longer display commands in the command list that the current user cannot run
  • Added Command.can_run(ctx), which runs through and makes sure that all of the command checks pass.
  • Added backwards compatibility support for legacy media endpoints (servers that don't support matrix v1.11 yet). Authenticated media will still be used by default.
  • Python 3.13 is now officially supported in time for v1.2.0a2
  • niobot attachment types now support os.PathLike, as well as str, BytesIO, and Pathlib, in case you had some freaky custom path type
  • Overlapping typing events in anything using room_send (e.g. send_message, edit_message) will no-longer throw an error if there is a mid-air collision. Instead, a warning will be logged to the stream, and the operation will be a no-op. This may cause some inconsistencies in the typing indicators sent by nio-bot, however that is preferrable to errors.
  • You can now have a little bit more control over the sync loop
    • niobot.NioBot now allows you to pass a static presence (online, unavailable, offline), False to outright disable presence, and None (default) to set it automatically based on the startup stage (recommended for slower connections)
    • You can now, if you needed to for some reason, disable full state sync via sync_full_state=False.
  • Fixed niobot.NioBot.join throwing a JSON EOF in some cases
  • Added the reason parameter to niobot.NioBot.join and niobot.NioBot.room_leave as optional strings
  • NioBot's auto-join feature now uses this to include a reason when automatically joining rooms
  • Fixed module event handlers, in debug logs, being named as anonymous functions, rather than their true names. This will make debugging issues with your event handlers easier.
  • Removed the password login critical log in favour of simply documenting the dangers of using a password
  • niobot.NioBot.send_message will now automatically parse mentions if not explicitly provided, to take full advantage of intentional mentions.
  • Added force_initial_sync to niobot.NioBot, which will force the bot to sync all rooms before starting the event loop.
  • DM rooms are now removed properly from account data when leaving.
  • Fixed niobot.NioBot.on_event not properly accepting raw nio.Event types
  • Fixed some faulty sync responses triggering commands twice
  • Fixed a bug in the default help command that would display hidden commands regardless.
  • Removed fallback replies in messages (see: MSC2781)
"},{"location":"changelog/#v111-2024-06-26","title":"v1.1.1 (2024-06-26)","text":"
  • Heavy CI improvements (2024-05-08 -> 2024-06-15)
  • Deprecated unimplemented force_write parameter in some BaseAttachment (and subclass) methods. (2024-06-15)
"},{"location":"changelog/#v110post3-2024-04-16","title":"v1.1.0.post3 (2024-04-16)","text":""},{"location":"changelog/#new-features","title":"New features","text":"
  • Added CHANGELOG.md (and consequently, this page) to documentation. (2024-02-08)
  • NioBot._get_id now tells you what object it couldn't identify in the raised exception. (2024-02-11)
  • NioBot.mount_module now warns you if you define a custom setup() that doesn't update the command or event register. (2024-02-11)
"},{"location":"changelog/#v110post2-2024-02-08","title":"v1.1.0.post2 (2024-02-08)","text":""},{"location":"changelog/#new-features_1","title":"New features","text":"
  • Added auto_read_messages key word argument to NioBot to automatically read messages from rooms. Defaults to True. Disabling this (False) will prevent read reciepts from automatically being sent.
"},{"location":"changelog/#bug-fixes","title":"Bug fixes","text":"
  • Fixed NioBot.get_dm_rooms raising a 401 Unauthorised error regardless of any state.
  • Fixed NioBot.get_dm_rooms raising a GenericMatrixError whenever there were no DM rooms, instead of gracefully returning an empty object.
  • Fixed NioBot.get_dm_rooms using outdated code from before matrix-nio==0.24.0.
"},{"location":"changelog/#v110-2024-01-30","title":"v1.1.0 (2024-01-30)","text":"

The license changed in this release.

With release v1.1.0 (specifically commit 421414d), the license for nio-bot was changed from GPLv3 to LGPLv3. In short, this means you do not have to open source your code, and you are able to commercialise your project if you use nio-bot.

This version's changelog includes changes made in its pre-release versions

This changelog includes all changes made since the last stable release, including those made in pre-release versions. If you scroll down, you will see duplicate feature changelogs where the feature was changed in a pre-release version.

"},{"location":"changelog/#new-features_2","title":"New features","text":"
  • Added niobot.Context.invoking_prefix.
  • Python 3.12 is now supported.
  • Added niobot.NioBot.is_ready, which is an asyncio.Event.
  • Added command-level checks (@niobot.check)
  • Added sparse DM room support.
  • Added additional exception types, such as GenericMatrixError.
  • Additional type-hinting for the entire library.
"},{"location":"changelog/#changes","title":"Changes","text":"
  • License changed from GPLv3 to LGPLv3.
  • Improved existing type-hinting.
  • event_id is prioritised over room_id in niobot.NioBot._get_id.
  • niobot was changed to nio-bot (for consistency) throughout documentation and the pip installation guide.
"},{"location":"changelog/#v110b1post1-and-v110b1-2023-10-16","title":"v1.1.0b1.post1 (and v1.1.0b1) (2023-10-16)","text":""},{"location":"changelog/#new-features_3","title":"New features","text":"
  • Added CI testing to the library.
  • Rewrote argument parsers to use a class-based ABC system, rather than a function-based system. See documentation.
  • Added the ignore_self flag to niobot.NioBot, allowing you to choose whether the client will ignore its own messages.
  • Added support for typing.Annotated in commands.
"},{"location":"changelog/#deprecations-removals","title":"Deprecations & Removals","text":"
  • The property niobot.Module.log was fully removed - it was never fully functional and often tripped up users as it was unsettable.
  • The property niobot.Module.client was deprecated - you should use niobot.Module.client instead.
"},{"location":"changelog/#v110a3-2023-10-06","title":"v1.1.0a3 (2023-10-06)","text":""},{"location":"changelog/#changes_1","title":"Changes","text":"
  • Prioritise event_id over room_id for the _get_id function
  • Add Context.invoking_prefix
  • Type hinting and code refactor
"},{"location":"changelog/#v110a2-2023-08-21","title":"v1.1.0a2 (2023-08-21)","text":""},{"location":"changelog/#new-features_4","title":"New features","text":"
  • Backported support to Python 3.9 and Python 3.10.
"},{"location":"changelog/#bug-fixes_1","title":"Bug fixes","text":"
  • Fixed a bug where disabled commands could crash the command parser.
"},{"location":"changelog/#documentation-changes","title":"Documentation changes","text":"
  • Replaced niobot with nio-bot for pip install guide.
  • Fixed PyPi link in README.
  • Cleaned up documentation issues.
  • Removed the examples on GitHub (until the package is more stable in terms of design).
"},{"location":"changelog/#v110a1-2023-07-31","title":"v1.1.0a1 (2023-07-31)","text":""},{"location":"changelog/#new-features_5","title":"New features","text":"
  • Added documentation for events.
  • Added niobot.attachments.which function.
  • Added very early DM room support.
  • Added easier ways to customise the help command.
  • Added more specific exception types.
  • Added event_parser and room_parser
"},{"location":"changelog/#changes_2","title":"Changes","text":"
  • force_await now just awaits coroutines rather than casting them to a task
  • Command arguments now properly raise the correct errors
"},{"location":"changelog/#v102-2023-07-16","title":"v1.0.2 (2023-07-16)","text":"

This is an urgent security release - denial of service vulnerability.

This release fixes a vulnerability where a potentially accidentally crafted message payload could cause the bot to completely crash. If you had disabled ignoring old messages, this could cause a crash loop if your bot automatically restarted.

If you cannot update to v1.0.2 from a previous release, you should implement the following workaround:

import niobot\n\n\nclass PatchedClient(niobot.NioBot):\n    async def process_message(self, *args):\n        try:\n            await super().process_message(*args)\n        except IndexError:  # IndexError is the only error thrown during parsing\n            pass  # or print, whatever\n\n# bot = niobot.NioBot(...)\nbot = PatchedClient(...)  # use your patched version\n
"},{"location":"changelog/#new-features_6","title":"New features","text":"
  • Added niobot.attachments.get_image_metadata (depends on imagemagick)
  • niocli version now shows the OS and CPU architecture.
  • niobot.attachment.* now always imports all attachment types into the niobot namespace, regardless of installed external dependencies.
"},{"location":"changelog/#bug-fixes_2","title":"Bug fixes","text":"
  • Fixed niobot.ImageAttachment being unable to detect image streams.
  • Fixed niobot.BaseAttachment setting incorrect file properties
  • niobot.ImageAttachment no longer explicitly fails upon encountering an unknown format, it simply emits a warning, in line with niobot.VideoAttachment.
  • Fixed an unexpected input causing the entire process to crash.
"},{"location":"changelog/#v101-2023-07-12","title":"v1.0.1 (2023-07-12)","text":"
  • Added stub setup.py for really old pip versions.
  • Updated the README.
"},{"location":"changelog/#v100-2023-07-12","title":"v1.0.0 (2023-07-12)","text":"

The first stable release! This version has many breaking changes since v0.1.0, and as such is not very backwards compatible.

  • MediaAttachment and Thumbnail were split up into ImageAttachment, VideoAttachment, AudioAttachment, and FileAttachment.
  • Attachments now automatically detect metadata.
  • Thumbnailing was massively improved.
  • Blurhashes are automatically generated for images.
  • Attachments now fully support end-to-end encryption.
  • Attachments will now emit warnings when a non web-safe codec is used.
  • Automatic command parameter parsing, so you no longer have to manually specify Command(arguments=[...]).
  • Automatic help command sanitisation.
  • Added additional requirements.
  • Added the ability to add and remove reactions.
  • Added __repr__ to most objects in the library.
  • Added more helper/utility functions.
  • Added documentation (tada!).
  • Added more customisation options to niobot.NioBot.

You've reached the end! There are no previously documented releases before the big 1.0.0. If you want to expand this list, you can contribute on GitHub! Open issues, or even better, make some pull requests. We love new contributors!

"},{"location":"guides/001-getting-started/","title":"Getting started / quick start","text":"

Want to skip to see some examples?

You can see some examples on GitHub

So, you've joined matrix, had a look around, and now you want to make your own little bot? Guess what, you can do just that with nio-bot!

"},{"location":"guides/001-getting-started/#prerequisites","title":"Prerequisites","text":"

You will need the following in general:

  • A matrix account you can log into (username and password initially)

And the following installed on the machine you want to run the bot on:

  • Python with sqlite support
  • libolm (use your system package manager, like apt or pacman) in order to use end-to-end encryption.
  • libmagic (usually you can just install python3-magic) which is required for attachments.
  • A decent network connection (at least a few megabits a second, preferably more)
  • At least 100mb free storage space (for the database and other files)
"},{"location":"guides/001-getting-started/#installation","title":"Installation","text":"

After you've installed and acquired the above, you can install nio-bot with the following command:

python3 -m pip install nio-bot[cli]\n# Note that we install the extras for `cli` here - the niobot CLI comes with a bunch of useful tools we'll use.\n
If you would like to install support for end-to-end encryption, you can install the following instead:
python3 -m pip install nio-bot[cli,e2ee]\n

"},{"location":"guides/001-getting-started/#creating-the-start-of-your-bot","title":"Creating the start of your bot","text":"

In our instance here, we'll create a few files:

  1. A config.py file to store our configuration.
  2. A main.py file to store our bot code.
  3. A fun.py file to store a module (later on).

And you'll need a directory:

  1. store - this is where nio-bot will store its database and other files.
"},{"location":"guides/001-getting-started/#file-structure","title":"File structure","text":"

And as such, our directory structure will look like this:

test-niobot/\n\u251c\u2500\u2500 config.py\n\u251c\u2500\u2500 fun.py\n\u251c\u2500\u2500 requirements.txt\n\u251c\u2500\u2500 store/\n\u2514\u2500\u2500 main.py\n

Danger

Make sure, if you are using version control, to add config.py to your .gitignore file! This file contains all of your personal information, such as your password, and should not be shared with anyone.

While you're at it, you should add the store directory to your .gitignore file as well, as that will contain encryption keys later on.

"},{"location":"guides/001-getting-started/#setting-up-configpy","title":"Setting up config.py","text":"

For this example, we will assume you are using https://matrix.org as your homeserver.

In our config.py file, we'll add the following:

HOMESERVER = \"https://matrix-client.matrix.org\"\nUSER_ID = \"@my-username:matrix.org\"\nPASSWORD = \"my-password\"\n

Warning

Make sure to replace the above with your own homeserver, user ID, and password!

"},{"location":"guides/001-getting-started/#making-the-bot-runtime-file","title":"Making the bot runtime file","text":"

And, to make a simple bot, you can just copy the below template into your main.py file:

import niobot\nimport config\n\nbot = niobot.NioBot(\n    homeserver=config.HOMESERVER,\n    user_id=config.USER_ID,\n    device_id='my-device-id',\n    store_path='./store',\n    command_prefix=\"!\",\n    owner_id=\"@my-matrix-username:matrix.org\"\n)\n# We also want to load `fun.py`'s commands before starting:\nbot.mount_module(\"fun\")  # looks at ./fun.py\n\n@bot.on_event(\"ready\")\nasync def on_ready(_):\n    # That first argument is needed as the first result of the sync loop is passed to ready. Without it, this event\n    # will fail to fire, and will cause a potentially catasrophic failure.\n    print(\"Bot is ready!\")\n\n\n@bot.command()\nasync def ping(ctx):  # can be invoked with \"!ping\"\n    await ctx.respond(\"Pong!\")\n\nbot.run(password=config.PASSWORD)\n
About owner_id

owner_id is intended to tell nio-bot who owns the current instance. Do not set this to be the same thing as config.USER_ID (your bot's ID)! The only time you should do that is if you want to run the bot on the same account you use.

Otherwise, set this to be your own user ID, so that you can use any \"owner only\" commands.

It is not necessary to set this though, so it can be omitted or set to None. Just note that NioBot.is_owner(...) will raise an error when used in that case.

"},{"location":"guides/001-getting-started/#enabling-logging","title":"Enabling logging","text":"

You'll probably find that it's useful to enable debug logging while you're developing your bot. To do that, you can add the following to your main.py file:

import logging\nimport niobot\n\nlogging.basicConfig(level=logging.DEBUG)\n# or to save to a file (uncomment):\n# logging.basicConfig(level=logging.DEBUG, filename=\"bot.log\")\n\nbot = niobot.NioBot(...)\n...\n

This will print an awful lot of text to your console, but will ultimately be helpful for debugging any issues you are encountering, as it will show you a lot of what niobot is doing.

"},{"location":"guides/001-getting-started/#making-funpy","title":"Making fun.py","text":"

Now, fun.py is going to be a module.

Modules are a great way to organize your code, and make it easier to manage. They also allow you to easily add new commands to your bot without having to edit the main file, which means you can split your code up, and make it... modular!

To start, we need to make the fun.py python file, and add the following:

import niobot\n\n\nclass MyFunModule(niobot.Module):  # subclassing niobot.Module is mandatory for auto-detection.\n    def __init__(self, bot):\n        super().__init__(bot)\n        # This gives you access to `self.bot`, which is the NioBot instance you made in main.py!\n
And that's it! You made your module!

"},{"location":"guides/001-getting-started/#but-wait-theres-more","title":"But wait, there's more!","text":"

You may notice that with this being a separate module, you can't use @bot.command, or @bot.on_event, or reference bot at all!

You'd initially assume \"Oh, I'll just import bot from main.\" - but that's not going to work. The reason for this is every time main.py is imported, it creates a new NioBot, and then... calls bot.run() at the end, meaning not only would your import never finish, but it would also cause a massive recursion bug!

The way you get around this is instead with @niobot.command(). This is a decorator that will register the command with the bot, however is designed specifically with modules in mind.

Let's compare the two, for simplicity:

@niobot.NioBot.command() @niobot.command() Adds commands to the register immediately Adds commands to the register once the module is loaded Can only be used at runtime, or wherever bot can be imported from Can only be used in modules (has no effect outside aniobot.Module!) Takes priority over @niobot.command() due to the immediate register Lower priority than NioBot.command() due to the \"lazy loading\"

Do be aware though, both decorators will take the exact same arguments as niobot.Command.

"},{"location":"guides/001-getting-started/#adding-a-command-to-funpy","title":"Adding a command to fun.py","text":"

So, let's add a command to our module:

import niobot\n\n\nclass MyFunModule(niobot.Module):  # subclassing niobot.Module is mandatory for auto-detection.\n    def __init__(self, bot):\n        super().__init__(bot)\n\n    @niobot.command()\n    async def hello(self, ctx: niobot.Context):\n        await ctx.respond(\"Hello %s!\" % ctx.event.sender)\n

This will add a command, !hello, that will reply with \"Hello {@author}!\"

"},{"location":"guides/001-getting-started/#starting-the-bot","title":"Starting the bot","text":"

Hold your horses, you're not quite ready yet!

Generally, it's a terrible idea to always use a password in your code. It's a security risk, and in matrix it can result in creating many sessions, which you don't want, especially if you're using encryption!

"},{"location":"guides/001-getting-started/#getting-an-access-token","title":"Getting an access token","text":"

An access token is like a server-generated long-lived password. You will probably want one in order to repeatedly use the same session, and to avoid having to use your password in your code.

You can get your password with niocli get-access-token. For example:

(venv) [me@host test-niobot]$ niocli get-access-token\nUser ID (@username:homeserver.tld): @test:matrix.org\nPassword (will not echo):\nDevice ID (a memorable display name for this login, such as 'bot-production') [host]:\nResolving homeserver... OK\nGetting access token... OK\nAccess token: syt_<...>\n

What you're going to do now, is copy the full access token string, and open config.py again Now, replace PASSWORD=... with ACCESS_TOKEN=\"syt_<...>\". Make sure to keep the quotes!

You will also need to go into main.py, down to the last line, and replace password=config.PASSWORD with access_token=config.ACCESS_TOKEN.

What is sso_token?

SSO token is a Single Sign On token, employed by the likes of Mozilla, and is often used for SAML. Chances are, if you don't know what it is, you definitely don't need it. And if you do need it, you already know what it is, why you need it, and how to get it.

"},{"location":"guides/001-getting-started/#actually-running-the-bot","title":"Actually running the bot","text":"

This is the really simple part, actually. All you need to do now is run main.py!

(venv) [me@host test-niobot]$ python main.py\n<insert log spam here>\nBot is ready!\n<insert log spam here>\n

Its taking FOREVER to log in! is something going wrong?

Nope. It can often take a while (upwards of five minutes in some cases!) for the bot to log in. This is because, when you first start the bot, it has to sync your entire state with the server. This often results in a LOT of IO, and a lot of network waiting, etc.

You can speed up this process in the future by:

  • Making sure you have store_path and a valid store in your configuration. Stores mean that the bot doesn't have to re-sync everything every time it starts up.
  • Using an access token instead of a password. This means that the bot doesn't have to log in, and can just start syncing immediately, even from the last time it was stopped, which saves a very very large portion of the time taken
"},{"location":"guides/001-getting-started/#interesting-log-output","title":"Interesting log output","text":"

You may notice that, if you enabled logging, you get some interesting log output.

Some things you will want to keep an eye out for:

  • INFO:niobot.client:Encryption support enabled automatically. - This means that you have set up requirements for the bot to use encryption, and it has detected that it can use encryption, and automatically enabled it, which is good!
  • DEBUG:niobot.client:<module '...' from '...'> does not have its own setup() - auto-discovering commands and events - This means that the bot has detected a module, and is automatically loading it. This is good for most cases. You should only worry about this message if you defined your own setup function.
  • DEBUG:niobot.client:Registered command <Command name='...' aliases=[...] disabled=...> into <command_name> - This simply means a command has been added to the internal register.
  • DEBUG:niobot.client:Added event listener <function <function_name> at <address>> for '<event_name>' - Like the above, this simply means an event has been added to the internal register.
"},{"location":"guides/001-getting-started/#and-thats-it","title":"And that's it!","text":"

You've successfully made a bot, and got it running!

"},{"location":"guides/001-getting-started/#wait-how-do-i-use-it","title":"Wait, how do I use it?","text":"

nio-bot has a handy dandy auto-join feature - if you just invite your bot's user to a room, assuming all is correct, within a couple seconds, your bot will automatically join your room!

Then, you can run !help to get a list of commands, and !help <command> to get help on a specific command.

"},{"location":"guides/001-getting-started/#final-product","title":"Final product","text":"config.py
HOMESERVER = \"https://matrix.org\"  # or your homeserver\nUSER_ID = \"@my-bot:matrix.org\"  # your bot account's user ID\nACCESS_TOKEN = \"syt_<...>\"  # your bot account's access token\n
main.py
import niobot\nimport logging\nimport config\n\nlogging.basicConfig(level=logging.INFO, filename=\"bot.log\")\n\nbot = niobot.NioBot(\n    homeserver=config.HOMESERVER,\n    user_id=config.USER_ID,\n    device_id='my-device-id',\n    store_path='./store',\n    command_prefix=\"!\",\n    owner_id=\"@my-matrix-username:matrix.org\"\n)\n# We also want to load `fun.py`'s commands before starting:\nbot.mount_module(\"fun\")\n\n@bot.on_event(\"ready\")\nasync def on_ready(_):\n    # That first argument is needed as the first result of the sync loop is passed to ready. Without it, this event\n    # will fail to fire, and will cause a potentially catasrophic failure.\n    print(\"Bot is ready!\")\n\n\n@bot.command()\nasync def ping(ctx):  # can be invoked with \"!ping\"\n    await ctx.respond(\"Pong!\")\n\nbot.run(access_token=config.ACCESS_TOKEN)\n
fun.py
import niobot\n\n\nclass MyFunModule(niobot.Module):  # subclassing niobot.Module is mandatory for auto-detection.\n    def __init__(self, bot):\n        self.bot = bot  # bot is the NioBot instance you made in main.py!\n\n    @niobot.command()\n    async def hello(self, ctx):\n        await ctx.respond(\"Hello %s!\" % ctx.event.sender)\n
"},{"location":"guides/001-getting-started/#why-is-logging-in-with-a-password-so-bad","title":"Why is logging in with a password so bad?","text":"

Logging in with a password carries more risk than logging in with a token, and is more prone to error. When you log in with a password with nio-bot, you need to store that password. If your password is leaked, an attacker can create as many sessions as they want, reset your password, etc. However, with an access token, they're limited to that session, and cannot reset your password.

Furthermore, if you log in with a password, some servers may generate you a completely new session, which is very slow, and can easily break any e2ee you have. Unless you are very careful with your environment, you may find yourself with slow startup times, session spam, and decryption errors.

What you should do instead is get an access token.

If you already know how to get yours, that's great! Otherwise, niocli has the solution:

$ niocli get-access-token\n

This will log into the account when prompted, and will grab you an access token, spitting it out into your terminal.

From there, you can replace bot.run(password=\"...\") with bot.run(access_token=\"...\"), and you're good to go!

"},{"location":"guides/002-a-simple-bot/","title":"A simple bot - example","text":"

Let's take the following code and break it down, so that you can better understand how niobot works:

import niobot\n\n\nbot = niobot.NioBot(\n    homeserver=\"https://matrix.org\",\n    user_id=\"@my_username:matrix.org\",\n    device_id=\"my_device_name\",  # defaults to the easily recognisable 'nio-bot'.\n    store_path=\"./store\",  # See the 'What is the store?' section for more info.\n    command_prefix=\"!\",\n    case_insensitive=True,  # means `!PING` and `!piNG` will both work and run `!ping`.\n    owner_id=\"@owner:matrix.org\",  # The user ID who owns this bot. Optional, but required for bot.is_owner(...).#\n    # Here, you can pass extra options that you would usually pass to `nio.AsyncClient`. Lets pass a proxy:\n    proxy=\"socks5://username:password@host:port\"\n)\n\n\n# Now, we want to register a command. NioBot uses the decorator `@NioBot.command()` for this.\n# This decorator takes a few arguments, but the only one you'll use most of the time is `name`.\n# There are also other arguments though.\n# For now we'll register a simple `ping` command.\n@bot.command(name=\"ping\")\n# We now need to define the function\nasync def ping(ctx: niobot.Context):\n    \"\"\"Shows the latency between events\"\"\"\n    # So a few things have happened here:\n    # `async def` makes this command asynchronous. This means that it can `await` other things.\n    # `ctx` is the only argument this command takes. By default, all niobot commands must take at least one argument,\n    # which is the command's context.\n    # We also then typehinted it with `niobot.Context`. This isn't critical to make the bot run, however if you're using\n    # an IDE like PyCharm, or just a workspace editor with intellisense like Visual Studio Code, it will help you\n    # to see what attributes and functions `niobot.Context` has without needing to check the documentation.\n    # Anyway, lets write the command itself.\n    # First, we need to measure the latency. NioBot has a handy function for this:\n    latency_ms = bot.latency(ctx.message)\n    # `bot.latency` measures the latency between when the event was dispatched by the server, and when the bot\n    # received it. It then returns the latency in milliseconds.\n    # `Context.message` is the event that triggered this command.\n    # Now, we need to reply to the user.\n    await ctx.respond(\"Pong! Latency: {:.2f}ms\".format(latency_ms))\n    # And that's it! We've written our first command.\n    # `Context.respond` always sends a reply to the user who sent the command.\n    # To send a message without a reply, you can use `NioBot.send_message`.\n\n\n# And while we're at it, we can add an event listener.\n# For this example, we'll add an event listener that tells the user if there's a command error.\n@bot.on_event(\"command_error\")\nasync def on_command_error(ctx: niobot.Context, error: Exception):\n    \"\"\"Called when a command raises an exception\"\"\"\n    # Take a look at the event reference for more information about events.\n    # Now, we can send a message to the user.\n    await ctx.respond(\"Error: {}\".format(error))\n\n\n# And while we're at it, we'll log when a user runs a command.\n@bot.on_event(\"command\")\nasync def on_command(ctx):\n    print(\"User {} ran command {}\".format(ctx.message.sender, ctx.command.name))\n\n\n# Now, we need to start our bot.\n# This is done by calling `NioBot.run()`.\n# In this example, we'll use an access token, rather than an insecure password.\n# You can get an access token through the niobot CLI:\n# $ niocli get-access-token\n# Copy the resulting access token, and then you can put it here:\nbot.run(access_token=\"my_access_token\")\n# Bear in mind that no code will run after `bot.run`. This function will block until the bot is stopped.\n# And even when the bot is stopped, its usually with an exception, so code after `bot.run` is not guaranteed to run.\n
"},{"location":"guides/003-sending-attachments/","title":"Sending attachments","text":"

Sometimes, you want to upload attachments in your chat. Be that images, videos, or other kinds of files. NioBot supports this, and it's very easy to do.

"},{"location":"guides/003-sending-attachments/#before-you-start","title":"Before you start","text":"

In order to use the majority of the features in this guide, you will need to install ffmpeg and imagemagick. These are used for thumbnail generation, and metadata detection.

You should use your package manager to install these, as they are not python packages.

Debian/UbuntuArchFedoramacOSWindows
sudo apt install ffmpeg imagemagick\n
sudo pacman -S ffmpeg imagemagick\n
sudo dnf install ffmpeg imagemagick\n
brew install ffmpeg imagemagick\n

choco install ffmpeg\nchoco install imagemagick\n
Or, install it yourself. Make sure the binaries are in your PATH:

  • gyan.dev/ffmpeg/builds/ffmpeg-git-essentials.7z
  • imagemagick.org/script/download.php
"},{"location":"guides/003-sending-attachments/#faq","title":"FAQ","text":"Why do I need to install ffmpeg and imagemagick?

imagemagick is actually optional - if you trust ffprobe to work with all of your images (in some cases it can fail to detect newer image formats), then you can skip installing it.

However, ffmpeg is required for all but file attachments. This is because in order to get some rich data, such as dimensions and duration, we need to use ffprobe to get this data. Furthermore, in the event imagemagick is not installed, the metadata fetcher falls back to ffprobe.

Not having these installed will result in a RuntimeError being raised when you try to send an attachment when it tries to fetch metadata. This is because the metadata fetcher will not be able to find ffprobe or imagemagick in your PATH.

Why does it take a couple of seconds for <attachment>.from_file() to return?

The from_file method (see: niobot.VideoAttachment.from_file, niobot.ImageAttachment.from_file, etc.) does a lot of heavy lifting in terms of preparing a file with all the bells and whistles for an upload. This means that it has to do a lot of processing, which may take a couple of seconds to return.

"},{"location":"guides/003-sending-attachments/#sending","title":"Sending:","text":""},{"location":"guides/003-sending-attachments/#regular-files","title":"Regular files","text":"

Here, regular files can be anything that isn't a video, image, or audio file. This includes text files, PDFs, etc. You can even send binary or pre-encrypted (why?) files if you want to.

Regular files are the simplest file type in niobot, in terms of code complexity and also features. Regular files do not support:

  • Thumbnails
  • Rich data
  • Previews

All you get from thumbnails is the file name, and the file size. That's it.

Anyway, here's how you could send an example text (foo.txt) file:

from niobot import NioBot, Context, FileAttachment\n...\n\n@bot.comand(name=\"upload.txt\")\nasync def upload_txt(ctx: Context):\n    \"\"\"Sends a text file!\"\"\"\n    attachment = await FileAttachment.from_file(\"file.txt\")\n    await ctx.respond(file=attachment)\n

This results in the following:

You can then click on the file to download it!

"},{"location":"guides/003-sending-attachments/#images","title":"Images","text":"

Images are a bit more complex than regular files. They support thumbnails, rich data, and previews.

Thumbnails for images

While you may think that thumbnails for images are useless, they are actually very useful for clients. Just beware though, having a larger or equal size image for your thumbnail is very counter-productive.

A valid use case for image thumbnails is for lower-resolution, likely compressed versions of the image you're sending. Paired with a blurhash, this can provide a very good \"placeholder\" image for people on painfully slow connections.

For your convenience, unless disabled, niobot will automatically generate a \"blurhash\" for your image.

A blurhash is very good for providing a \"placeholder\" image, as it is generated by a string of around 30 characters. This means people on super slow connections can see a pretty preview of the image (without much detail), instead of having an ugly loading spinner or outright blank space in place of a loading image.

For example:

This may slow down your image upload

Generating blurhashes, especially for large images, even more especially with a weak CPU, can be very slow. While this will not block your code execution, it means you must wait for the blurhash to be generated before you can do anything with the image.

You may want to disable this behaviour. See disabling extra media features.

And here's an example:

from niobot import NioBot, Context, ImageAttachment\n...\n\n\n@bot.comand(name=\"upload.png\")\nasync def upload_png(ctx: Context):\n    \"\"\"Sends a png image!\"\"\"\n    attachment = await ImageAttachment.from_file(\"file.png\")\n    await ctx.respond(file=attachment)\n

"},{"location":"guides/003-sending-attachments/#audio","title":"Audio","text":"

Audio files are actually simpler than images, however they do not support thumbnails or rich data outside of their duration.

Beware your codec!

You should aim to have your audio files as opus, vorbis, aac, flac, or mp3 encoded files, as some clients may not be able to play other formats. Also be mindful of their containers, since some (such as mkv) won't play in some clients.

Here's an example:

from niobot import NioBot, Context, AudioAttachment\n...\n\n\n@bot.comand(name=\"upload.mp3\")\nasync def upload_mp3(ctx: Context):\n    \"\"\"Sends a mp3 audio file!\"\"\"\n    attachment = await AudioAttachment.from_file(\"file.mp3\")\n    await ctx.respond(file=attachment)\n

"},{"location":"guides/003-sending-attachments/#videos","title":"Videos","text":"

Videos are the most complex file type in niobot. They support thumbnails, rich data, and previews.

Again though, NioBot makes this easy. All you need to do is pass a video file to VideoAttachment.from_file().

The same warnings apply as images, except for the blurhash. Blurhashes are not generated for videos. However, thumbnails are generated by default, with their own blurhashes. For simplicity, the video's auto-generated thumbnail is simply the first frame of the video.

Beware of your codec(s)!

A lot of matrix clients at the moment are simple HTML5-based clients - meaning they can only play a limited set of codecs out of the box.

You should aim to keep your video codecs as h264, vp8, or vp9, as these are the most widely supported. However, some native apps may not even support vp8/vp9. Use h264/avc when in doubt.

Look at audio's warning for more information about audio codecs.

Here's an example:

from niobot import NioBot, Context, VideoAttachment\n...\n\n\n@bot.comand(name=\"upload.mp4\")\nasync def upload_mp4(ctx: Context):\n    \"\"\"Sends a mp4 video!\"\"\"\n    attachment = await VideoAttachment.from_file(\"file.mp4\")\n    await ctx.respond(file=attachment)\n

"},{"location":"guides/003-sending-attachments/#unsure-which-to-use","title":"Unsure which to use?","text":"

If you aren't sure which file type you have, you can find out the most appropriate Attachment type using niobot.attachment.which - this will return either VideoAttachment, ImageAttachment, AudioAttachment, or FileAttachment, based on the mime type of the file.

Fpr example:

import random\nimport niobot\n\n\n...\n\n\n@bot.comand(name=\"upload\")\nasync def upload_mp4(ctx: Context):\n    \"\"\"Sends a random file!\"\"\"\n    files = (\"file.txt\", \"file.mp4\", \"file.mp3\", \"file.png\")\n    file_name = random.choice(files)\n\n    attachment_type = niobot.which(file_name)\n    attachment = await attachment_type.from_file(file_name)\n    # ^ can also be written as `attachment = await niobot.which(file_name).from_file(file_name)`\n    await ctx.respond(file=attachment)\n

This will upload using the appropriate file type.

"},{"location":"guides/003-sending-attachments/#disabling-extra-media-features","title":"Disabling extra media features","text":""},{"location":"guides/003-sending-attachments/#disabling-blurhash-generation","title":"Disabling blurhash generation","text":"This will harm the user experience

Disabling blurhash generation is a terrible idea - unless you make sure your uploads are a matter of kilobytes, you will always see blank spots while at least a thumbnail is loaded. Please consider alternative options.

for niobot.VideoAttachment and niobot.ImageAttachment:

from niobot import NioBot, Context, ImageAttachment, VideoAttachment\n...\n\nasync def foo():\n    attachment = await ImageAttachment.from_file(\"file.png\", generate_blurhash=False)\n    # or for Videos\n    attachment = await VideoAttachment.from_file(\"file.mp4\", generate_blurhash=False)\n

"},{"location":"guides/003-sending-attachments/#disabling-thumbnail-generation","title":"Disabling thumbnail generation","text":"This will harm the user experience

If you intend to disable thumbnail generation, you should provide your own thumbnail, or at the very least leave blurhash generation enabled.

Otherwise, while your video loads, clients will most likely just show a completely transparent box, with a loading spinner at a stretch. This leaves a massive chunk of the UI completely blank while your video loads.

for niobot.VideoAttachment only:

from niobot import NioBot, Context, ImageAttachment, VideoAttachment\n...\n\nasync def foo():\n    attachment = await VideoAttachment.from_file(\"file.mp4\", thumbnail=False)\n

"},{"location":"guides/003-sending-attachments/#disabling-rich-data","title":"Disabling rich data","text":"

A lot of rich data fields will still require values for clients to properly render the media!

In this case, \"rich data\" refers to some \"optional\" fields in media uploads, such as height, width, duration, etc. These fields are not required for the server to accept the upload, but they are often used by clients to figure out how to properly display the media.

\"Rich data\" is gathered from the get_metadata function, which itself calls ffprobe/imagemagick as a subprocess. If, for whatever reason, this is undesirable, you can avoid it.

Disabling rich data is not 100% possible, but you can avoid it by passing minimal values where it would automatically be filled in:

"},{"location":"guides/003-sending-attachments/#images_1","title":"Images","text":"
from niobot import ImageAttachment\n\nasync def foo():\n    attachment = await ImageAttachment.from_file(\"file.png\", width=0, height=0, unsafe=True)\n
"},{"location":"guides/003-sending-attachments/#videos_1","title":"Videos","text":"

from niobot import VideoAttachment\n\nasync def foo():\n    attachment = await VideoAttachment.from_file(\"file.mp4\", width=0, height=0, duration=0)\n
You may also want to consider either manually passing a thumbnail, or disabling thumbnail auto generation, as otherwise you'll still have ffmpeg/imagemagick called.

"},{"location":"guides/003-sending-attachments/#audio_1","title":"Audio","text":"
from niobot import AudioAttachment\n\nasync def foo():\n    attachment = await AudioAttachment.from_file(\"file.mp3\", duration=0)\n
"},{"location":"guides/004-creating-custom-parsers/","title":"Creating custom parsers","text":"

New in version 1.1.0b1

This feature was added in version 1.1.0b1 - pip install niobot>=1.1.0b1

NioBot is loaded with some sane defaults for basic types. If you were to pass the following function as a command:

async def my_command(ctx: Context, arg1: int, arg2: float, arg3: str):\n    ...\n
NioBot would intelligently detect ctx as the context (and realise it's not intended to be something the user provides) and arg1 as an integer, arg2 as a float, and arg3 as a string. Then, when my_command is run, the first argument would be converted to an integer from the message, the second as a float, and so on.

However, these built-in types only go so far - they're limited to a subset of python built-in types, and a couple matrix-specific things (i.e. rooms and events and matrix.to links).

This looks worryingly complicated

Pre 1.1.0b1, parsers were far too flexible and inconsistent. The old structure only required you had a singular synchronous function that took three arguments: ctx, arg, and user_input.

This was a problem for a couple reasons:

  1. The flexibility meant that it was difficult to get a uniform experience across all parsers.
  2. This was still not very flexible for customising the parsers, and often required wrappers.

However, since 1.1.0b1, the parser structure now uses two new ABC classes, Parser and StatelessParser, to ensure that all parsers are consistent and easy to use, while still being flexible and configurable. As a result of using classes though, some parsers can still feel a little bit bulky. But that's okay!

"},{"location":"guides/004-creating-custom-parsers/#creating-a-parser","title":"Creating a parser","text":"

Creating a parser is actually really easy. All the library needs from you is a class that subclasses either of the parser ABCs (see below), and implements the __call__ dunder method!

For example:

from niobot.utils.parsers import StatelessParser\nfrom niobot import CommandParserError\n\n\nclass UserParser(StatelessParser):\n    def __call__(self, ctx: Context, arg: Argument, value: str):\n        # Do some stuff here\n        if \"@\" not in value:\n            # Always raise CommandParserError when its an invalid value - this allows for proper error handling.\n            raise CommandParserError(\"Invalid user ID. Expected @user:example.com\")\n        return value[1:]  # Remove the @ from the user ID\n

You can then use this parser in your commands like so:

import niobot\nimport typing\nfrom my_parsers import UserParser\n\n\nbot = niobot.NioBot(...)\n\n\n@bot.command()\nasync def my_command(ctx: niobot.Context, user: typing.Annotated[str, UserParser]):\n    # typing.Annotated[real_type, parser] is a special type that allows you to specify a parser for a type.\n    # In your linter, `user` will be `str`, not `UserParser`.\n    await ctx.respond(\"User ID: {!s}\".format(user))\n

"},{"location":"guides/004-creating-custom-parsers/#what-if-i-need-to-await-in-my-parser","title":"What if I need to await in my parser?","text":"

If you need to use asynchronous functions in your parser, you can simply return the coroutine in __call__, like below:

class MyParser(Parser):\n    async def internal_caller(self, ctx: Context, arg: Argument, value: str):\n        # Do some stuff here\n        await asyncio.sleep(1)  # or whatever async function you need to call\n        return value\n\n    def __call__(self, *args, **kwargs):\n        return self.internal_caller(*args, **kwargs)  # this returns a coroutine.\n
By returning the unawaited coroutine, the library will intelligently detect it needs to be awaited, and will do so.

If you want to use a parser like this in your code manually, you can always use niobot.utils.force_await, which will await a coroutine if it needs awaiting, or simply returns the input if it's not a coroutine.

from niobot.utils import force_await\ncoro = MyParser()(...)\n# If you're not sure if coro is a coroutine or not, you can use force_await\nparsed = await force_await()\n# Otherwise, simply await the result\ncoro = await MyParser()(...)\n
"},{"location":"guides/004-creating-custom-parsers/#whats-the-difference-between-parser-and-statelessparser","title":"What's the difference between Parser and StatelessParser?","text":"

Great question!

With parsers, there's often a split between complicated/customisable, and fixed parsers. For example, IntegerParser is a customisable parser - You can pass options to it while initialising it, and it will use those options to parse the input. However, on the contrary, BooleanParser is a fixed parser - it does not take any options, and will always convert the input to a boolean.

Basically, StatelessParser never needs to access self while parsing. Parser can.

"},{"location":"guides/004-creating-custom-parsers/#which-should-i-choose","title":"Which should I choose?","text":"

If you're writing a parser that needs to be customisable and takes options, then you should use Parser. Otherwise, if you don't need self, then you should use StatelessParser.

"},{"location":"guides/005-direct-messages/","title":"Direct Messages","text":"

New in version 1.1.0b2

This feature was added in version 1.1.0b2 - pip install niobot>=1.1.0b2

In Matrix, Direct Messages are a bit of a loose concept. In short, a \"direct message\" in Matrix is more of a room, except with initially only two members, and a special flag, is_direct, set to true in the invite event.

However, when you join a direct room, or flag a room as direct in a client, this information is stored in account data, on the homeserver. This means that your homeserver will keep track of rooms that you're in that're flagged as \"direct\".

Direct does not mean one-to-one!

Direct rooms are not necessarily one-to-one. They can have more than two members, and they can be group chats.

The only thing that makes a room \"direct\" is the is_direct flag in the invite event.

This means that if you want to send a message to a user, you should check if the direct room you've chosen contains only two members. If not, look for another, or create one.

"},{"location":"guides/005-direct-messages/#how-do-i-send-dms-in-niobot","title":"How do I send DMs in NioBot?","text":"

NioBot handles DMs mostly transparently.

In base matrix-nio, trying to use AsyncClient.room_send and passing a user as the room_id will result in an error. You'd probably expect it to send a message to that user, so NioBot does just that!

With a bit of magic, NioBot will automatically create a direct room with the user, and send the message there. In addition, if there's already a direct room stored in account data, NioBot will use the first one it finds.

Take this example:

import niobot\n\nbot = niobot.NioBot(...)\n\n\n@bot.command(\"dm\")\nasync def send_dm(ctx: niobot.Context):\n    \"\"\"Sends you a direct message!\"\"\"\n    await bot.send_message(ctx.message.sender, \"Hello, world!\")\n

First, NioBot checks to see if there's a direct room stored in account data. If there is, it'll use that. If not, however, it will create one, and invite the user.

And that's it! Its really that simple!

"},{"location":"guides/005-direct-messages/#getting-and-creating-direct-rooms","title":"Getting and creating direct rooms","text":"

If you want to use direct rooms outside of sending messages, you can use niobot.NioBot.get_dm_rooms, and niobot.NioBot.create_dm_room.

For example:

import niobot\n\nbot = niobot.NioBot(...)\n\n\n@bot.command(\"get-dm-rooms\")\nasync def get_dm_room(ctx: niobot.Context):\n    \"\"\"Gets the direct room with the user.\"\"\"\n    rooms = await bot.get_dm_rooms(ctx.message.sender)\n\n    if not rooms:\n        await ctx.respond(\"You don't have any direct rooms!\")\n        return\n\n    rooms_text = \"\\n\".join([f\"* https://matrix.to/#/{room_id}\" for room_id in rooms])\n    await ctx.respond(f\"Your {len(rooms):,} direct rooms:\\n\\n{rooms_text}\")\n\n\n@bot.command(\"create-dm-room\")\nasync def create_dm_room(ctx: niobot.Context):\n    \"\"\"Creates a direct room with the user.\"\"\"\n    response = await bot.create_dm_room(ctx.message.sender)\n    await ctx.respond(f\"Created direct room: https://matrix.to/#/{response.room_id}\")\n

In this example, get-dm-rooms would return a count, alongside a list, of every DM room the client shares with the user. create-dm-room would create a new direct room with the user, and return the room link.

The user would've already been automatically invited to the room when it was created, so there's no need to send an invitation separately.

"},{"location":"meta/code-of-conduct/","title":"Code of Conduct - niobot chat rooms","text":"

While we try to be nice and relaxed in our chat rooms, there are still a few guidelines that you must follow when engaging with our community.

For clarification, \"chat room\" here refers to any method of communication surrounding nio-bot. This includes, but is not limited to, the Matrix rooms, and GitHub issues. Read further to see more specific moderation information.

"},{"location":"meta/code-of-conduct/#ye-old-rule-book","title":"Ye old rule book","text":"

To keep things short and snappy, not every single punishable offense is listed here. Use a bit of common sense and think, \"would I want this in my community?\". If not, neither do we.

"},{"location":"meta/code-of-conduct/#1-serve-a-warm-and-kind-environment","title":"1. Serve a warm and kind environment","text":"

Conversations in the niobot chat rooms should strive to be a warm and welcoming place for anyone and everyone. This means:

  • All conversations should be civil and respectful.
  • Drama should be taken to direct messages, unless it directly relates to niobot.
  • Hurtful language should never be used, under any circumstances, ever. This includes language that may be hateful, bigoted, or hurtful.
    • Swearing is permitted, under the authority that it is not excessive or directed.
  • Messages should have clear intent - tone is difficult to convey over text.
  • Respect the people around you.
    • If someone asks you to stop doing something, you should do so. If you have an issue with their request, take it up with them directly in direct messages, or elsewhere.
"},{"location":"meta/code-of-conduct/#2-do-not-be-disruptive","title":"2. Do not be disruptive","text":"

Causing disruption can happen in many forms, but regardless, is entirely unwelcome. Being disruptive is usually in the form of sending spam, such as large blocks of text, or several images, but could also be injecting an off-topic conversation.

"},{"location":"meta/code-of-conduct/#3-act-without-malice","title":"3. Act without malice","text":"

Any form of malice is not welcome in the niobot chat rooms. Doing ANYTHING with malice is completely unacceptable and will be treated as such. Even if you do not intend malice, if malice is perceived by a group, and you cannot appropriately explain why it was not such, then it will be treated so.

"},{"location":"meta/code-of-conduct/#4-the-standard-dont-be-a-thorn","title":"4. The standard \"don't be a thorn\"","text":"

For this, you may want to take a gander at the Matrix.org code of conduct. Their CoC has a great list of things that you should not do here, such as doxxing, harassment, or posting anything NSFW.

Their code of conduct conduct also applies to our chat rooms as it is a great CoC, aside from:

  • Contact: see contact
  • Application: Their CoC applies to matrix.org, and our rooms.
  • Affiliation: We are not affiliated with matrix.org, we are simply using their CoC in good faith.
"},{"location":"meta/code-of-conduct/#moderation","title":"Moderation","text":"

Moderation in niobot matrix rooms is done by the Draupnir (@draupnir:nexy7574.co.uk) bot. This bot has the power level to mute, kick, ban, and serverban (ACL) in all niobot rooms.

Our instance of Draupnir is subscribed to the following moderation lists:

  • Matrix.org's ToS banlist
  • Matrix.org's CoC banlist
  • envs.net's banlist
  • The Community moderation effort (CME) banlist

Meaning, if your homeserver, or your account, is on any of those ban lists, you will not be able to communicate with our Matrix rooms. Futhermore, we also maintain our own custom ban list, however this is usually only exclusive to us before it is added to the CME.

If you violate the code of conduct, depending on the severity of the infraction, you will receive:

  1. A personal warning
  2. A temporary mute
  3. A kick
  4. A ban

Obviously, doing something like posting NSFW images will get you banned straight away, but simply getting into an argument won't.

For other platforms, moderation will be done manually, and as such more harshly.

"},{"location":"meta/code-of-conduct/#contact","title":"Contact","text":"

You can contact anyone in the niobot rooms that you see are a moderator (minus bots), however @nex ((me) in most places) is usually the administrator. You can contact me outside of matrix by looking at the \"contact\" section of my website. If that is not working, you can send an email to admin@ (domain can be nexy7574.co.uk or i-am.nexus), but I cannot guarantee a timely response.

"},{"location":"reference/attachment/","title":"Attachments","text":"

Matrix file attachments. Full e2ee support is implemented.

"},{"location":"reference/attachment/#niobot.attachment.AttachmentType","title":"AttachmentType","text":"

Bases: Enum

Enumeration containing the different types of media.

Attributes:

Name Type Description FILE 'AttachmentType'

A generic file.

AUDIO 'AttachmentType'

An audio file.

VIDEO 'AttachmentType'

A video file.

IMAGE 'AttachmentType'

An image file.

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment","title":"BaseAttachment","text":"

Bases: ABC

Base class for attachments

Note

If you pass a custom file_name, this is only actually used if you pass a io.BytesIO to file. If you pass a pathlib.Path or a string, the file name will be resolved from the path, overriding the file_name parameter.

Parameters:

Name Type Description Default file Union[str, BytesIO, PathLike, Path]

The file path or BytesIO object to upload.

required file_name Optional[str]

The name of the file. Must be specified if uploading a BytesIO object.

None mime_type Optional[str]

The mime type of the file. If not specified, it will be detected.

None size_bytes Optional[int]

The size of the file in bytes. If not specified, it will be detected.

None attachment_type AttachmentType

The type of attachment. Defaults to AttachmentType.FILE.

FILE

Attributes:

Name Type Description file Union[Path, BytesIO]

The file path or BytesIO object to upload. Resolved to a pathlib.Path object if a string is passed to __init__.

file_name str

The name of the file. If file was a string or Path, this will be the name of the file.

mime_type str

The mime type of the file.

size int

The size of the file in bytes.

type AttachmentType

The type of attachment.

url Optional[str]

The URL of the uploaded file. This is set after the file is uploaded.

keys Optional[dict[str, str]]

The encryption keys for the file. This is set after the file is uploaded.

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.size_bytes","title":"size_bytes property","text":"
size_bytes: int\n

Returns the size of this attachment in bytes.

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.as_body","title":"as_body","text":"
as_body(body: Optional[str] = None) -> dict\n

Generates the body for the attachment for sending. The attachment must've been uploaded first.

Parameters:

Name Type Description Default body Optional[str]

The body to use (should be a textual description). Defaults to the file name.

None

Returns:

Type Description dict"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.from_file","title":"from_file async classmethod","text":"
from_file(\n    file: Union[str, BytesIO, Path],\n    file_name: Optional[str] = None,\n) -> \"BaseAttachment\"\n

Creates an attachment from a file.

You should use this method instead of the constructor, as it will automatically detect all other values

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file or BytesIO to attach

required file_name Optional[str]

The name of the BytesIO file, if applicable

None

Returns:

Type Description 'BaseAttachment'

Loaded attachment.

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.from_mxc","title":"from_mxc async classmethod","text":"
from_mxc(client: 'NioBot', url: str) -> 'BaseAttachment'\n

Creates an attachment from an MXC URL.

Parameters:

Name Type Description Default client 'NioBot'

The current client instance (used to download the attachment)

required url str

The MXC:// url to download

required

Returns:

Type Description 'BaseAttachment'

The downloaded and probed attachment.

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.from_http","title":"from_http async classmethod","text":"
from_http(\n    url: str, client_session: Optional[ClientSession] = None\n) -> \"BaseAttachment\"\n

Creates an attachment from an HTTP URL.

This is not necessarily just for images, video, or other media - it can be used for any HTTP resource.

Parameters:

Name Type Description Default url str

The http/s URL to download

required client_session Optional[ClientSession]

The aiohttp client session to use. If not specified, a new one will be created.

None

Returns:

Type Description 'BaseAttachment'

The downloaded and probed attachment.

Raises:

Type Description niobot.MediaDownloadException

if the download failed.

aiohttp.ClientError

if the download failed.

niobot.MediaDetectionException

if the MIME type could not be detected.

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.size_as","title":"size_as","text":"
size_as(\n    unit: Literal[\n        \"b\", \"kb\", \"kib\", \"mb\", \"mib\", \"gb\", \"gib\"\n    ]\n) -> Union[int, float]\n

Helper function to convert the size of this attachment into a different unit.

Remember: - 1 kilobyte (KB) is 1000 bytes - 1 kibibyte (KiB) is 1024 bytes

Example

>>> import niobot\n>>> attachment = niobot.FileAttachment(\"background.png\", \"image/png\")\n>>> attachment.size_bytes\n329945\n>>> attachment.size_as(\"kb\")\n329.945\n>>> attachment.size_as(\"kib\")\n322.2119140625\n>>> attachment.size_as(\"mb\")\n0.329945\n>>> attachment.size_as(\"mib\")\n0.31466007232666016\n
Note that due to the nature of floats, precision may be lost, especially the larger in units you go.

Parameters:

Name Type Description Default unit Literal['b', 'kb', 'kib', 'mb', 'mib', 'gb', 'gib']

The unit to convert into

required

Returns:

Type Description Union[int, float]

The converted size

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.upload","title":"upload async","text":"
upload(\n    client: \"NioBot\", encrypted: bool = False\n) -> \"BaseAttachment\"\n

Uploads the file to matrix.

Parameters:

Name Type Description Default client 'NioBot'

The client to upload

required encrypted bool

Whether to encrypt the attachment or not

False

Returns:

Type Description 'BaseAttachment'

The attachment

"},{"location":"reference/attachment/#niobot.attachment.FileAttachment","title":"FileAttachment","text":"

Bases: BaseAttachment

Represents a generic file attachment.

You should use VideoAttachment for videos, AudioAttachment for audio, and ImageAttachment for images. This is for everything else.

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to upload

required file_name Optional[str]

The name of the file

None mime_type Optional[str]

The mime type of the file

None size_bytes Optional[int]

The size of the file in bytes

None"},{"location":"reference/attachment/#niobot.attachment.ImageAttachment","title":"ImageAttachment","text":"

Bases: BaseAttachment

Represents an image attachment.

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to upload

required file_name Optional[str]

The name of the file

None mime_type Optional[str]

The mime type of the file

None size_bytes Optional[int]

The size of the file in bytes

None height Optional[int]

The height of the image in pixels (e.g. 1080)

None width Optional[int]

The width of the image in pixels (e.g. 1920)

None thumbnail Optional['ImageAttachment']

A thumbnail of the image. NOT a blurhash.

None xyz_amorgan_blurhash Optional[str]

The blurhash of the image

None

Attributes:

Name Type Description info Dict

A dict of info about the image. Contains h, w, mimetype, and size keys.

thumbnail

A thumbnail of the image. NOT a blurhash.

"},{"location":"reference/attachment/#niobot.attachment.ImageAttachment.info","title":"info property","text":"
info: Dict\n

returns the info dictionary for this image.

Use ImageAttachment.as_body()[\"info\"] instead.

"},{"location":"reference/attachment/#niobot.attachment.ImageAttachment.from_file","title":"from_file async classmethod","text":"
from_file(\n    file: Union[str, BytesIO, Path],\n    file_name: Optional[str] = None,\n    height: Optional[int] = None,\n    width: Optional[int] = None,\n    thumbnail: Optional[\"ImageAttachment\"] = None,\n    generate_blurhash: bool = True,\n    *,\n    xyz_amorgan_blurhash: Optional[str] = None,\n    unsafe: bool = False\n) -> \"ImageAttachment\"\n

Generates an image attachment

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to upload

required file_name Optional[str]

The name of the file (only used if file is a BytesIO)

None height Optional[int]

The height, in pixels, of this image

None width Optional[int]

The width, in pixels, of this image

None thumbnail Optional['ImageAttachment']

A thumbnail for this image

None generate_blurhash bool

Whether to generate a blurhash for this image

True xyz_amorgan_blurhash Optional[str]

The blurhash of the image, if known beforehand.

None unsafe bool

Whether to allow uploading of images with unsupported codecs. May break metadata detection.

False

Returns:

Type Description 'ImageAttachment'

An image attachment

"},{"location":"reference/attachment/#niobot.attachment.ImageAttachment.thumbnailify_image","title":"thumbnailify_image staticmethod","text":"
thumbnailify_image(\n    image: Union[Image, BytesIO, str, Path],\n    size: Tuple[int, int] = (320, 240),\n    resampling: Union[\"PIL.Image.Resampling\"] = BICUBIC,\n) -> Image\n

Helper function to thumbnail an image.

This function is blocking - you should use niobot.utils.run_blocking to run it.

Parameters:

Name Type Description Default image Union[Image, BytesIO, str, Path]

The image to thumbnail

required size Tuple[int, int]

The size to thumbnail to. Defaults to 320x240, a standard thumbnail size.

(320, 240) resampling Union['PIL.Image.Resampling']

The resampling filter to use. Defaults to PIL.Image.BICUBIC, a high-quality but fast resampling method. For the highest quality, use PIL.Image.LANCZOS.

BICUBIC

Returns:

Type Description Image

The thumbnail

"},{"location":"reference/attachment/#niobot.attachment.ImageAttachment.get_blurhash","title":"get_blurhash async","text":"
get_blurhash(\n    quality: Tuple[int, int] = (4, 3),\n    file: Optional[Union[str, Path, BytesIO, Image]] = None,\n    disable_auto_crop: bool = False,\n) -> str\n

Gets the blurhash of the attachment. See: woltapp/blurhash

You should crop-down your blurhash images.

Generating blurhashes can take a long time, especially on large images. You should crop-down your images to a reasonable size before generating the blurhash.

Remember, most image quality is lost - there's very little point in generating a blurhash for a 4K image. Anything over 800x600 is definitely overkill.

You can easily resize images with niobot.ImageAttachment.thumbnailify_image:

attachment = await niobot.ImageAttachment.from_file(my_image, generate_blurhash=False)\nawait attachment.get_blurhash(file=attachment.thumbnailify_image(attachment.file))\n

This will generate a roughly 320x240 thumbnail image, and generate the blurhash from that.

New!

Unless you pass disable_auto_crop=True, this function will automatically crop the image down to a reasonable size, before generating a blurhash.

Parameters:

Name Type Description Default quality Tuple[int, int]

A tuple of the quality to generate the blurhash at. Defaults to (4, 3).

(4, 3) file Optional[Union[str, Path, BytesIO, Image]]

The file to generate the blurhash from. Defaults to the file passed in the constructor.

None disable_auto_crop bool

Whether to disable automatic cropping of the image. Defaults to False.

False

Returns:

Type Description str

The blurhash

"},{"location":"reference/attachment/#niobot.attachment.VideoAttachment","title":"VideoAttachment","text":"

Bases: BaseAttachment

Represents a video attachment.

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to upload

required file_name Optional[str]

The name of the file

None mime_type Optional[str]

The mime type of the file

None size_bytes Optional[int]

The size of the file in bytes

None height Optional[int]

The height of the video in pixels (e.g. 1080)

None width Optional[int]

The width of the video in pixels (e.g. 1920)

None duration Optional[int]

The duration of the video in seconds

None thumbnail Optional['ImageAttachment']

A thumbnail of the video. NOT a blurhash.

None"},{"location":"reference/attachment/#niobot.attachment.VideoAttachment.from_file","title":"from_file async classmethod","text":"
from_file(\n    file: Union[str, BytesIO, Path],\n    file_name: Optional[str] = None,\n    duration: Optional[int] = None,\n    height: Optional[int] = None,\n    width: Optional[int] = None,\n    thumbnail: Optional[\n        Union[ImageAttachment, Literal[False]]\n    ] = None,\n    generate_blurhash: bool = True,\n) -> \"VideoAttachment\"\n

Generates a video attachment

This function auto-generates a thumbnail!

As thumbnails greatly improve user experience, even with blurhashes enabled, this function will by default create a thumbnail of the first frame of the given video if you do not provide one yourself. This may increase your initialisation time by a couple seconds, give or take!

If this is undesirable, pass thumbnail=False to disable generating a thumbnail. This is independent of generate_blurhash.

Generated thumbnails are always WebP images, so they will always be miniature, so you shouldn't notice a significant increase in upload time, especially considering your video will likely be several megabytes.

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to upload

required file_name Optional[str]

The name of the file (only used if file is a BytesIO)

None duration Optional[int]

The duration of the video, in seconds

None height Optional[int]

The height, in pixels, of this video

None width Optional[int]

The width, in pixels, of this video

None thumbnail Optional[Union[ImageAttachment, Literal[False]]]

A thumbnail for this image

None generate_blurhash bool

Whether to generate a blurhash for this image

True

Returns:

Type Description 'VideoAttachment'

An image attachment

"},{"location":"reference/attachment/#niobot.attachment.VideoAttachment.generate_thumbnail","title":"generate_thumbnail async staticmethod","text":"
generate_thumbnail(\n    video: Union[str, Path, \"VideoAttachment\"]\n) -> ImageAttachment\n

Generates a thumbnail for a video.

Parameters:

Name Type Description Default video Union[str, Path, 'VideoAttachment']

The video to generate a thumbnail for

required

Returns:

Type Description ImageAttachment

The path to the generated thumbnail

"},{"location":"reference/attachment/#niobot.attachment.AudioAttachment","title":"AudioAttachment","text":"

Bases: BaseAttachment

Represents an audio attachment.

"},{"location":"reference/attachment/#niobot.attachment.AudioAttachment.duration","title":"duration property writable","text":"
duration: Optional[int]\n

The duration of this audio in milliseconds

"},{"location":"reference/attachment/#niobot.attachment.AudioAttachment.from_file","title":"from_file async classmethod","text":"
from_file(\n    file: Union[str, BytesIO, Path],\n    file_name: Optional[str] = None,\n    duration: Optional[int] = None,\n) -> \"AudioAttachment\"\n

Generates an audio attachment

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to upload

required file_name Optional[str]

The name of the file (only used if file is a BytesIO)

None duration Optional[int]

The duration of the audio, in seconds

None

Returns:

Type Description 'AudioAttachment'

An audio attachment

"},{"location":"reference/attachment/#niobot.attachment.detect_mime_type","title":"detect_mime_type","text":"
detect_mime_type(file: Union[str, BytesIO, Path]) -> str\n

Detect the mime type of a file.

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to detect the mime type of. Can be a BytesIO.

required

Returns:

Type Description str

The mime type of the file (e.g. text/plain, image/png, application/pdf, video/webp etc.)

"},{"location":"reference/attachment/#niobot.attachment.get_metadata_ffmpeg","title":"get_metadata_ffmpeg","text":"
get_metadata_ffmpeg(\n    file: Union[str, Path]\n) -> dict[str, Any]\n

Gets metadata for a file via ffprobe.

example output (JSON)

Parameters:

Name Type Description Default file Union[str, Path]

The file to get metadata for. Must be a path-like object

required

Returns:

Type Description dict[str, Any]

A dictionary containing the metadata.

"},{"location":"reference/attachment/#niobot.attachment.get_metadata_imagemagick","title":"get_metadata_imagemagick","text":"
get_metadata_imagemagick(file: Path) -> dict[str, Any]\n

The same as get_metadata_ffmpeg but for ImageMagick.

Only returns a limited subset of the data, such as one stream, which contains the format, and size, and the format, which contains the filename, format, and size.

example output (JSON)

Parameters:

Name Type Description Default file Path

The file to get metadata for. Must be a path object

required

Returns:

Type Description dict[str, Any]

A slimmed-down dictionary containing the metadata.

"},{"location":"reference/attachment/#niobot.attachment.get_metadata","title":"get_metadata","text":"
get_metadata(\n    file: Union[str, Path], mime_type: Optional[str] = None\n) -> dict[str, Any]\n

Gets metadata for a file.

This will use imagemagick (identify) for images where available, falling back to ffmpeg (ffprobe) for everything else.

Parameters:

Name Type Description Default file Union[str, Path]

The file to get metadata for.

required mime_type Optional[str]

The mime type of the file. If not provided, it will be detected.

None

Returns:

Type Description dict[str, Any]

The metadata for the file. See niobot.get_metadata_ffmpeg and niobot.get_metadata_imagemagick for more information.

"},{"location":"reference/attachment/#niobot.attachment.first_frame","title":"first_frame","text":"
first_frame(\n    file: Union[str, Path], file_format: str = \"webp\"\n) -> bytes\n

Gets the first frame of a video file.

This function creates a file on disk

In order to extract the frame, this function creates a temporary file on disk (or memdisk depending on where your tempdir is). While this file is deleted after the function is done, it is still something to be aware of. For example, if you're (worryingly) low on space, this function may fail to extract the frame due to a lack of space. Or, someone could temporarily access and read the file before it is deleted.

This also means that this function may be slow.

Parameters:

Name Type Description Default file Union[str, Path]

The file to get the first frame of. Must be a path-like object

required file_format str

The format to save the frame as. Defaults to webp.

'webp'

Returns:

Type Description bytes

The first frame of the video in bytes.

"},{"location":"reference/attachment/#niobot.attachment.generate_blur_hash","title":"generate_blur_hash","text":"
generate_blur_hash(\n    file: Union[str, Path, BytesIO, Image], *parts: int\n) -> str\n

Creates a blurhash

This function may be resource intensive

This function may be resource intensive, especially for large images. You should run this in a thread or process pool.

You should also scale any images down in order to increase performance.

See: woltapp/blurhash

"},{"location":"reference/attachment/#niobot.attachment.which","title":"which","text":"
which(\n    file: Union[BytesIO, Path, str],\n    mime_type: Optional[str] = None,\n) -> Union[\n    Type[\"FileAttachment\"],\n    Type[\"ImageAttachment\"],\n    Type[\"AudioAttachment\"],\n    Type[\"VideoAttachment\"],\n]\n

Gets the correct attachment type for a file.

This function will provide either Image/Video/Audio attachment where possible, or FileAttachment otherwise.

For example, image/png (from my_image.png) will see image/ and will return ImageAttachment, and video/mp4 (from my_video.mp4) will see video/ and will return VideoAttachment.

If the mime type cannot be mapped to an attachment type, this function will return FileAttachment.

Usage
import niobot\nimport pathlib\n\nmy_file = pathlib.Path(\"/tmp/foo.bar\")\nattachment = await niobot.which(my_file).from_file(my_file)\n# or\nattachment_type = niobot.which(my_file)  # one of the BaseAttachment subclasses\nattachment = await attachment_type.from_file(my_file)\n

Parameters:

Name Type Description Default file Union[BytesIO, Path, str]

The file or BytesIO to investigate

required mime_type Optional[str]

The optional pre-detected mime type. If this is not provided, it will be detected.

None

Returns:

Type Description Union[Type['FileAttachment'], Type['ImageAttachment'], Type['AudioAttachment'], Type['VideoAttachment']]

The correct type for this attachment (not instantiated)

"},{"location":"reference/client/","title":"Client","text":""},{"location":"reference/client/#niobot.client.NioBot","title":"NioBot","text":"

Bases: AsyncClient

The main client for NioBot.

Forcing an initial sync is slow

(for the force_initial_sync parameter) By default, nio-bot stores what the last sync token was, and will resume from that next time it starts. This allows you to start up near instantly, and makes development easier and faster.

However, in some cases, especially if you are missing some metadata such as rooms or their members, you may need to perform an initial (sometimes referred to as \"full\") sync. An initial sync will fetch ALL the data from the server, rather than just what has changed since the last sync.

This initial sync can take several minutes, especially the larger your bot gets, and should only be used if you are missing aforementioned data that you need.

Parameters:

Name Type Description Default homeserver str

The homeserver to connect to. e.g. https://matrix-client.matrix.org

required user_id str

The user ID to log in as. e.g. @user:matrix.org

required device_id str

The device ID to log in as. e.g. nio-bot

'nio-bot' store_path Optional[str]

The path to the store file. Defaults to ./store. Must be a directory.

None command_prefix Union[str, Pattern, Iterable[str]]

The prefix to use for commands. e.g. !. Can be a string, a list of strings, or a regex pattern.

required case_insensitive bool

Whether to ignore case when checking for commands. If True, this casefold()s incoming messages for parsing.

True global_message_type Literal['m.text', 'm.notice']

The message type to default to. Defaults to m.notice

'm.notice' ignore_old_events bool

Whether to simply discard events before the bot's login.

True auto_join_rooms bool

Whether to automatically join rooms the bot is invited to.

True auto_read_messages bool

Whether to automatically update read recipts

True owner_id Optional[str]

The user ID of the bot owner. If set, only this user can run owner-only commands, etc.

None max_message_cache int

The maximum number of messages to cache. Defaults to 1000.

1000 ignore_self bool

Whether to ignore messages sent by the bot itself. Defaults to False. Useful for self-bots.

True import_keys Tuple[PathLike, Optional[str]]

A key export file and password tuple. These keys will be imported at startup.

None startup_presence Literal['online', 'unavailable', 'offline', False, None]

The presence to set on startup. False disables presence altogether, and None is automatic based on the startup progress.

None default_parse_mentions bool

Whether to parse mentions in send_message by default to make them intentional.

True force_initial_sync bool

Forcefully perform a full initial sync at startup.

False use_fallback_replies bool

Whether to force the usage of deprecated fallback replies. Not recommended outside of compatibility reasons.

False"},{"location":"reference/client/#niobot.client.NioBot.supported_server_versions","title":"supported_server_versions property","text":"
supported_server_versions: List[Tuple[int, int, int]]\n

Returns the supported server versions as a list of major, minor, patch tuples.

The only time patch is >0 is when the server is using a deprecated r release. All stable releases (v1) will have patch as 0.

This property returns [(1, 1, 0)] if no server info is available.

"},{"location":"reference/client/#niobot.client.NioBot.commands","title":"commands property","text":"
commands: dict[str, Command]\n

Returns the internal command register.

Warning

Modifying any values here will update the internal register too.

Note

Aliases of commands are treated as their own command instance. You will see the same command show up as a value multiple times if it has aliases.

You can check if two commands are identical by comparing them (command1instance == command2instance)

"},{"location":"reference/client/#niobot.client.NioBot.modules","title":"modules property","text":"
modules: dict[Type, Module]\n

Returns the internal module register.

Warning

Modifying any values here will update the internal register too.

"},{"location":"reference/client/#niobot.client.NioBot.server_supports","title":"server_supports","text":"
server_supports(\n    version: Union[Tuple[int, int], Tuple[int, int, int]]\n) -> bool\n

Checks that the server supports at least this matrix version.

"},{"location":"reference/client/#niobot.client.NioBot.mxc_to_http","title":"mxc_to_http async","text":"
mxc_to_http(\n    mxc: str, homeserver: Optional[str] = None\n) -> Optional[str]\n

Converts an mxc:// URI to a downloadable HTTP URL.

This function is identical the nio.AsyncClient.mxc_to_http() function, however supports matrix 1.10 and below's unauthenticated media automatically.

Parameters:

Name Type Description Default mxc str

The mxc URI

required homeserver Optional[str]

The homeserver to download this through (defaults to the bot's homeserver)

None

Returns:

Type Description Optional[str]

an MXC URL, if applicable

"},{"location":"reference/client/#niobot.client.NioBot.latency","title":"latency staticmethod","text":"
latency(\n    event: Event, *, received_at: Optional[float] = None\n) -> float\n

Returns the latency for a given event in milliseconds

Parameters:

Name Type Description Default event Event

The event to measure latency with

required received_at Optional[float]

The optional time the event was received at. If not given, uses the current time.

None

Returns:

Type Description float

The latency in milliseconds

"},{"location":"reference/client/#niobot.client.NioBot.dispatch","title":"dispatch","text":"
dispatch(event_name: Union[str, Event], *args, **kwargs)\n

Dispatches an event to listeners

"},{"location":"reference/client/#niobot.client.NioBot.is_old","title":"is_old","text":"
is_old(event: Event) -> bool\n

Checks if an event was sent before the bot started. Always returns False when ignore_old_events is False

"},{"location":"reference/client/#niobot.client.NioBot.update_read_receipts","title":"update_read_receipts async","text":"
update_read_receipts(\n    room: Union[str, MatrixRoom], event: Event\n)\n

Moves the read indicator to the given event in the room.

This is automatically done for you.

Whenever a message is received, this is automatically called for you. As such, your read receipt will always be the most recent message. You rarely need to call this function.

Parameters:

Name Type Description Default room Union[str, MatrixRoom]

The room to update the read receipt in.

required event Event

The event to move the read receipt to.

required

Returns:

Type Description

Nothing

"},{"location":"reference/client/#niobot.client.NioBot.process_message","title":"process_message async","text":"
process_message(\n    room: MatrixRoom, event: RoomMessage\n) -> None\n

Processes a message and runs the command it is trying to invoke if any.

"},{"location":"reference/client/#niobot.client.NioBot.is_owner","title":"is_owner","text":"
is_owner(user_id: str) -> bool\n

Checks whether a user is the owner of the bot.

Parameters:

Name Type Description Default user_id str

The user ID to check.

required

Returns:

Type Description bool

Whether the user is the owner.

"},{"location":"reference/client/#niobot.client.NioBot.mount_module","title":"mount_module","text":"
mount_module(import_path: str) -> Optional[list[Command]]\n

Mounts a module including all of its commands.

Must be a subclass of niobot.commands.Module, or else this function will not work.

There may not be an event loop running when this function is called.

If you are calling this function before you call bot.run(), it is entirely possible that you don't have a running asyncio event loop. If you use the event loop in Module.__init__, you will get an error, and the module will fail the mount.

You can get around this by deferring mounting your modules until the ready event is fired, at which point not only will the first full sync have completed (meaning the bot has all of its caches populated), but the event loop will be running.

Parameters:

Name Type Description Default import_path str

The import path (such as modules.file), which would be ./modules/file.py in a file tree.

required

Returns:

Type Description Optional[list[Command]]

Optional[list[Command]] - A list of commands mounted. None if the module's setup() was called.

Raises:

Type Description ImportError

The module path is incorrect of there was another error while importing

TypeError

The module was not a subclass of Module.

ValueError

There was an error registering a command (e.g. name conflict)

"},{"location":"reference/client/#niobot.client.NioBot.unmount_module","title":"unmount_module","text":"
unmount_module(module: Module) -> None\n

Does the opposite of mounting the module. This will remove any commands that have been added to the bot from the given module.

Parameters:

Name Type Description Default module Module

The module to unmount

required"},{"location":"reference/client/#niobot.client.NioBot.get_command","title":"get_command","text":"
get_command(name: str) -> Optional[Command]\n

Attempts to retrieve an internal command

Parameters:

Name Type Description Default name str

The name of the command to retrieve

required

Returns:

Type Description Optional[Command]

The command, if found. None otherwise.

"},{"location":"reference/client/#niobot.client.NioBot.add_command","title":"add_command","text":"
add_command(command: Command) -> None\n

Adds a command to the internal register

if a name or alias is already registered, this throws a ValueError. Otherwise, it returns None.

"},{"location":"reference/client/#niobot.client.NioBot.remove_command","title":"remove_command","text":"
remove_command(command: Command) -> None\n

Removes a command from the internal register.

If the command is not registered, this is a no-op.

"},{"location":"reference/client/#niobot.client.NioBot.command","title":"command","text":"
command(name: Optional[str] = None, **kwargs)\n

Registers a command with the bot.

"},{"location":"reference/client/#niobot.client.NioBot.on_event","title":"on_event","text":"
on_event(\n    event_type: Optional[Union[str, Type[Event]]] = None\n)\n

Wrapper that allows you to register an event handler.

Event handlers must be async.

if event_type is None, the function name is used as the event type.

Please note that if you pass a Event, you are responsible for capturing errors.

"},{"location":"reference/client/#niobot.client.NioBot.set_room_nickname","title":"set_room_nickname async","text":"
set_room_nickname(\n    room: Union[str, MatrixRoom],\n    new_nickname: str = None,\n    user: Optional[Union[str, MatrixUser]] = None,\n) -> RoomPutStateResponse\n

Changes the user's nickname in the given room.

Parameters:

Name Type Description Default room Union[str, MatrixRoom]

The room to change the nickname in.

required new_nickname str

The new nickname. If None, defaults to the user's display name.

None user Optional[Union[str, MatrixUser]]

The user to update. Defaults to the bot's user.

None

Returns:

Type Description RoomPutStateResponse

The response from the server.

"},{"location":"reference/client/#niobot.client.NioBot.get_cached_message","title":"get_cached_message","text":"
get_cached_message(\n    event_id: str,\n) -> Optional[Tuple[MatrixRoom, RoomMessage]]\n

Fetches a message from the cache.

This returns both the room the message was sent in, and the event itself.

If the message is not in the cache, this returns None.

"},{"location":"reference/client/#niobot.client.NioBot.fetch_message","title":"fetch_message async","text":"
fetch_message(room_id: str, event_id: str)\n

Fetches a message from the server.

"},{"location":"reference/client/#niobot.client.NioBot.wait_for_message","title":"wait_for_message async","text":"
wait_for_message(\n    room_id: Optional[str] = None,\n    sender: Optional[str] = None,\n    check: Optional[\n        Callable[[MatrixRoom, RoomMessageText], Any]\n    ] = None,\n    *,\n    timeout: Optional[float] = None,\n    msg_type: Type[RoomMessage] = RoomMessageText\n) -> Optional[Tuple[MatrixRoom, RoomMessage]]\n

Waits for a message, optionally with a filter.

If this function times out, asyncio.TimeoutError is raised.

Parameters:

Name Type Description Default room_id Optional[str]

The room ID to wait for a message in. If None, waits for any room.

None sender Optional[str]

The user ID to wait for a message from. If None, waits for any sender.

None check Optional[Callable[[MatrixRoom, RoomMessageText], Any]]

A function to check the message with. If the function returns False, the message is ignored.

None timeout Optional[float]

The maximum time to wait for a message. If None, waits indefinitely.

None msg_type Type[RoomMessage]

The type of message to wait for. Defaults to nio.RoomMessageText.

RoomMessageText

Returns:

Type Description Optional[Tuple[MatrixRoom, RoomMessage]]

The room and message that was received.

"},{"location":"reference/client/#niobot.client.NioBot.markdown_to_html","title":"markdown_to_html async staticmethod","text":"
markdown_to_html(text: str) -> str\n

Converts markdown to HTML.

Parameters:

Name Type Description Default text str

The markdown to render as HTML

required

Returns:

Type Description str

the rendered HTML

"},{"location":"reference/client/#niobot.client.NioBot.generate_mx_reply","title":"generate_mx_reply","text":"
generate_mx_reply(\n    room: MatrixRoom, event: RoomMessageText\n) -> str\n

Fallback replies have been removed by MSC2781. Do not use this anymore.

"},{"location":"reference/client/#niobot.client.NioBot.get_dm_rooms","title":"get_dm_rooms async","text":"
get_dm_rooms() -> Dict[str, List[str]]\n
get_dm_rooms(user: Union[MatrixUser, str]) -> List[str]\n
get_dm_rooms(\n    user: Optional[Union[MatrixUser, str]] = None\n) -> Union[Dict[str, List[str]], List[str]]\n

Gets DM rooms, optionally for a specific user.

If no user is given, this returns a dictionary of user IDs to lists of rooms.

Parameters:

Name Type Description Default user Optional[Union[MatrixUser, str]]

The user ID or object to get DM rooms for.

None

Returns:

Type Description Union[Dict[str, List[str]], List[str]]

A dictionary of user IDs to lists of rooms, or a list of rooms.

"},{"location":"reference/client/#niobot.client.NioBot.create_dm_room","title":"create_dm_room async","text":"
create_dm_room(\n    user: Union[MatrixUser, str]\n) -> RoomCreateResponse\n

Creates a DM room with a given user.

Parameters:

Name Type Description Default user Union[MatrixUser, str]

The user to create a DM room with.

required

Returns:

Type Description RoomCreateResponse

The response from the server.

"},{"location":"reference/client/#niobot.client.NioBot.send_message","title":"send_message async","text":"
send_message(\n    room: Union[MatrixRoom, MatrixUser, str],\n    content: Optional[str] = None,\n    file: Optional[BaseAttachment] = None,\n    reply_to: Optional[Union[RoomMessage, str]] = None,\n    message_type: Optional[str] = None,\n    *,\n    content_type: Literal[\n        \"plain\", \"markdown\", \"html\", \"html.raw\"\n    ] = \"markdown\",\n    override: Optional[dict] = None,\n    mentions: Union[Mentions, Literal[False], None] = None\n) -> RoomSendResponse\n

Sends a message. Doesn't get any more simple than this.

DMs

As of v1.1.0, you can now send messages to users (either a nio.MatrixUser or a user ID string), and a direct message room will automatically be created for you if one does not exist, using an existing one if it does.

Content Type

Separate to message_type, content_type controls what sort of parsing and formatting will be applied to the provided content. This is useful for sending messages that are not markdown, or for sending HTML. Before, all content was assumed to be markdown, and was parsed as such. However, this may cause undesirable effects if you are sending messages that are not markdown.

  • plain - No parsing or formatting is applied, and the content is sent as-is.
  • markdown - The content is parsed as markdown and rendered as HTML, with a fallback plain text body. This is the default.
  • html - The content is sent as HTML, with no fallback to plain text. If BeautifulSoup is installed, the provided content will be sanitised and pretty-printed before sending. ** html.raw - The content is sent as HTML, with no fallback to plain text, nor sanitising or formatting.

Parameters:

Name Type Description Default room Union[MatrixRoom, MatrixUser, str]

The room or to send this message to

required content Optional[str]

The content to send. Cannot be used with file.

None file Optional[BaseAttachment]

A file to send, if any. Cannot be used with content.

None reply_to Optional[Union[RoomMessage, str]]

A message to reply to.

None message_type Optional[str]

The message type to send. If none, defaults to NioBot.global_message_type, which itself is m.notice by default.

None override Optional[dict]

A dictionary containing additional properties to pass to the body. Overrides existing properties.

None content_type Literal['plain', 'markdown', 'html', 'html.raw']

The type of content to send. Defaults to \"markdown\".

'markdown' mentions Union[Mentions, Literal[False], None]

Intentional mentions to send with the message. If not provided, or False, then auto-detected.

None

Returns:

Type Description RoomSendResponse

The response from the server.

Raises:

Type Description MessageException

If the message fails to send, or if the file fails to upload.

ValueError

You specified neither file nor content.

RuntimeError

An internal error occured. A room was created, but is not in the bot room list.

"},{"location":"reference/client/#niobot.client.NioBot.edit_message","title":"edit_message async","text":"
edit_message(\n    room: Union[MatrixRoom, str],\n    message: Union[Event, str],\n    content: str,\n    *,\n    message_type: Optional[str] = None,\n    content_type: Literal[\n        \"plain\", \"markdown\", \"html\", \"html.raw\"\n    ] = \"markdown\",\n    mentions: Optional[Mentions] = None,\n    override: Optional[dict] = None\n) -> RoomSendResponse\n

Edit an existing message. You must be the sender of the message.

You also cannot edit messages that are attachments.

Parameters:

Name Type Description Default room Union[MatrixRoom, str]

The room the message is in.

required message Union[Event, str]

The message to edit.

required content str

The new content of the message.

required message_type Optional[str]

The new type of the message (i.e. m.text, m.notice. Defaults to client.global_message_type)

None override Optional[dict]

A dictionary containing additional properties to pass to the body. Overrides existing properties.

None content_type Literal['plain', 'markdown', 'html', 'html.raw']

The type of content to send. Defaults to \"markdown\".

'markdown'

Raises:

Type Description RuntimeError

If you are not the sender of the message.

TypeError

If the message is not text.

"},{"location":"reference/client/#niobot.client.NioBot.delete_message","title":"delete_message async","text":"
delete_message(\n    room: Union[MatrixRoom, str],\n    message_id: Union[RoomMessage, str],\n    reason: Optional[str] = None,\n) -> RoomRedactResponse\n

Delete an existing message. You must be the sender of the message.

Parameters:

Name Type Description Default room Union[MatrixRoom, str]

The room the message is in.

required message_id Union[RoomMessage, str]

The message to delete.

required reason Optional[str]

The reason for deleting the message.

None

Raises:

Type Description RuntimeError

If you are not the sender of the message.

MessageException

If the message fails to delete.

"},{"location":"reference/client/#niobot.client.NioBot.add_reaction","title":"add_reaction async","text":"
add_reaction(\n    room: Union[MatrixRoom, str],\n    message: Union[RoomMessage, str],\n    emoji: str,\n) -> RoomSendResponse\n

Adds an emoji \"reaction\" to a message.

Parameters:

Name Type Description Default room Union[MatrixRoom, str]

The room the message is in.

required message Union[RoomMessage, str]

The event ID or message object to react to.

required emoji str

The emoji to react with (e.g. \u274c = \u274c)

required

Returns:

Type Description RoomSendResponse

The response from the server.

Raises:

Type Description MessageException

If the message fails to react.

"},{"location":"reference/client/#niobot.client.NioBot.redact_reaction","title":"redact_reaction async","text":"
redact_reaction(\n    room: Union[MatrixRoom, str],\n    reaction: Union[RoomSendResponse, str],\n)\n

Alias for NioBot.delete_message, but more appropriately named for reactions.

"},{"location":"reference/client/#niobot.client.NioBot.start","title":"start async","text":"
start(\n    password: Optional[str] = None,\n    access_token: Optional[str] = None,\n    sso_token: Optional[str] = None,\n) -> None\n

Starts the bot, running the sync loop.

"},{"location":"reference/client/#niobot.client.NioBot.run","title":"run","text":"
run(\n    *,\n    password: Optional[str] = None,\n    access_token: Optional[str] = None,\n    sso_token: Optional[str] = None\n) -> None\n

Runs the bot, blocking the program until the event loop exists. This should be the last function to be called in your script, as once it exits, the bot will stop running.

Note: This function is literally just asyncio.run(NioBot.start(...)), so you won't have much control over the asyncio event loop. If you want more control, you should use await NioBot.start(...) instead.

Parameters:

Name Type Description Default password Optional[str]

The password to log in with.

None access_token Optional[str]

An existing login token.

None sso_token Optional[str]

An SSO token to sign in with.

None

Returns:

Type Description None"},{"location":"reference/client/#niobot.client.NioBot.get_account_data","title":"get_account_data async","text":"
get_account_data(\n    key: str, *, room_id: str = None\n) -> Union[dict, list, None]\n

Gets account data for the currently logged in account

Parameters:

Name Type Description Default key str

the key to get

required room_id str

The room ID to get account data from. If not provided, defaults to user-level.

None

Returns:

Type Description Union[dict, list, None]

The account data, or None if it doesn't exist

"},{"location":"reference/client/#niobot.client.NioBot.set_account_data","title":"set_account_data async","text":"
set_account_data(\n    key: str, data: dict, *, room_id: str = None\n) -> None\n

Sets account data for the currently logged in account

Parameters:

Name Type Description Default key str

the key to set

required data dict

the data to set

required room_id str

The room ID to set account data in. If not provided, defaults to user-level.

None"},{"location":"reference/client/#niobot.client.NioBot.join","title":"join async","text":"
join(\n    room_id: str, reason: str = None, is_dm: bool = False\n) -> Union[JoinResponse, JoinError]\n

Joins a room. room_id must be a room ID, not alias

Parameters:

Name Type Description Default room_id str

The room ID or alias to join

required reason str

The reason for joining the room, if any

None is_dm bool

Manually marks this room as a direct message.

False"},{"location":"reference/client/#niobot.client.NioBot.room_leave","title":"room_leave async","text":"
room_leave(\n    room_id: str, reason: str = None\n) -> Union[RoomLeaveError, RoomLeaveResponse]\n

Leaves a room. room_id must be an ID, not alias

"},{"location":"reference/commands/","title":"Commands","text":"

Using commands and events is the main way to interact with the bot.

"},{"location":"reference/commands/#niobot.commands.Argument","title":"Argument","text":"

Represents a command argument.

Example
from niobot import NioBot, command, Argument\n\nbot = NioBot(...)\n\n@bot.command(\"echo\")\ndef echo(ctx: niobot.Context, message: str):\n    await ctx.respond(message)\n\nbot.run(...)\n

Parameters:

Name Type Description Default name str

The name of the argument. Will be used to know which argument to pass to the command callback.

required arg_type _T

The type of the argument (e.g. str, int, etc. or a custom type)

required description Optional[str]

The description of the argument. Will be shown in the auto-generated help command.

None default Any

The default value of the argument

... required bool

Whether the argument is required or not. Defaults to True if default is ..., False otherwise.

... parser Callable[[Context, Argument, str], Optional[_T]]

A function that will parse the argument. Defaults to the default parser.

... greedy bool

When enabled, will attempt to match as many arguments as possible, without raising an error. If no arguments can be parsed, is merely empty, otherwise is a list of parsed arguments.

False"},{"location":"reference/commands/#niobot.commands.Argument.internal_parser","title":"internal_parser staticmethod","text":"
internal_parser(\n    _: Context, arg: Argument, value: str\n) -> Optional[_T]\n

The default parser for the argument. Will try to convert the value to the argument type.

"},{"location":"reference/commands/#niobot.commands.Command","title":"Command","text":"

Represents a command.

Example

Note

This example uses the command decorator, but you can also use the Command class directly, but you likely won't need to, unless you want to pass a custom command class.

All that the @command decorator does is create a Command instance and add it to the bot's commands, while wrapping the function its decorating.

from niobot import NioBot, command\n\nbot = NioBot(...)\n\n@bot.command(\"hello\")\ndef hello(ctx: niobot.Context):\n    await ctx.respond(\"Hello, %s!\" % ctx.message.sender)\n\nbot.run(...)\n

Parameters:

Name Type Description Default name str

The name of the command. Will be used to invoke the command.

required callback Callable

The callback to call when the command is invoked.

required aliases Optional[list[str]]

The aliases of the command. Will also be used to invoke the command.

None description Optional[str]

The description of the command. Will be shown in the auto-generated help command.

None disabled bool

Whether the command is disabled or not. If disabled, the command will be hidden on the auto-generated help command, and will not be able to be invoked.

False arguments Optional[list[Argument]]

A list of Argument instances. Will be used to parse the arguments given to the command. ctx is always the first argument, regardless of what you put here.

None usage Optional[str]

A string representing how to use this command's arguments. Will be shown in the auto-generated help. Do not include the command name or your bot's prefix here, only arguments. For example: usage=\"<message> [times]\" will show up as [p][command] <message> [times] in the help command.

None hidden bool

Whether the command is hidden or not. If hidden, the command will be always hidden on the auto-generated help.

False greedy bool

When enabled, CommandArgumentsError will not be raised if too many arguments are given to the command. This is useful for commands that take a variable amount of arguments, and retrieve them via Context.args.

False"},{"location":"reference/commands/#niobot.commands.Command.display_usage","title":"display_usage property","text":"
display_usage: str\n

Returns the usage string for this command, auto-resolved if not pre-defined

"},{"location":"reference/commands/#niobot.commands.Command.autodetect_args","title":"autodetect_args staticmethod","text":"
autodetect_args(callback) -> list[Argument]\n

Attempts to auto-detect the arguments for the command, based on the callback's signature

Parameters:

Name Type Description Default callback

The function to inspect

required

Returns:

Type Description list[Argument]

A list of arguments. self, and ctx are skipped.

"},{"location":"reference/commands/#niobot.commands.Command.__eq__","title":"__eq__","text":"
__eq__(other)\n

Checks if another command's runtime ID is the same as this one's

"},{"location":"reference/commands/#niobot.commands.Command.can_run","title":"can_run async","text":"
can_run(ctx: Context) -> bool\n

Checks if the current user passes all of the checks on the command.

If the user fails a check, CheckFailure is raised. Otherwise, True is returned.

"},{"location":"reference/commands/#niobot.commands.Command.invoke","title":"invoke async","text":"
invoke(ctx: Context) -> Coroutine\n

Invokes the current command with the given context

Parameters:

Name Type Description Default ctx Context

The current context

required

Raises:

Type Description CommandArgumentsError

Too many/few arguments, or an error parsing an argument.

CheckFailure

A check failed

"},{"location":"reference/commands/#niobot.commands.Command.construct_context","title":"construct_context","text":"
construct_context(\n    client: NioBot,\n    room: MatrixRoom,\n    src_event: RoomMessageText,\n    invoking_prefix: str,\n    meta: str,\n    cls: type = Context,\n) -> Context\n

Constructs the context for the current command.

You will rarely need to do this, the library automatically gives you a Context when a command is run.

Parameters:

Name Type Description Default client NioBot

The current instance of the client.

required room MatrixRoom

The room the command was invoked in.

required src_event RoomMessageText

The source event that triggered the command. Must be nio.RoomMessageText.

required invoking_prefix str

The prefix that triggered the command.

required meta str

The invoking string (usually the command name, however may be an alias instead)

required cls type

The class to construct the context with. Defaults to Context.

Context

Returns:

Type Description Context

The constructed Context.

"},{"location":"reference/commands/#niobot.commands.Module","title":"Module","text":"

Represents a module.

A module houses a set of commands and events, and can be used to modularise your bot, and organise commands and their respective code into multiple files and classes for ease of use, development, and maintenance.

Attributes:

Name Type Description bot

The bot instance this module is mounted to.

"},{"location":"reference/commands/#niobot.commands.Module.list_events","title":"list_events","text":"
list_events() -> Generator[dict, None, None]\n

Lists all the @event listeners registered in this module.

This returns the functions themselves. You can get the event name via result.__nio_event__[\"name\"].

"},{"location":"reference/commands/#niobot.commands.Module.__setup__","title":"__setup__","text":"
__setup__()\n

Setup function called once by NioBot.mount_module(). Mounts every command discovered.

.. warning: If you override this function, you should ensure that you call super().setup() to ensure that commands are properly registered.

"},{"location":"reference/commands/#niobot.commands.Module.__teardown__","title":"__teardown__","text":"
__teardown__()\n

Teardown function called once by NioBot.unmount_module(). Removes any command that was mounted.

.. warning: If you override this function, you should ensure that you call super().teardown() to ensure that commands are properly unregistered.

"},{"location":"reference/commands/#niobot.commands.command","title":"command","text":"
command(name: Optional[str] = None, **kwargs) -> Callable\n

Allows you to register commands later on, by loading modules.

This differs from NioBot.command() in that commands are not automatically added, you need to load them with bot.mount_module

Parameters:

Name Type Description Default name Optional[str]

The name of the command. Defaults to function.name

None kwargs

Any key-words to pass to Command

{}

Returns:

Type Description Callable"},{"location":"reference/commands/#niobot.commands.check","title":"check","text":"
check(\n    function: Callable[\n        [Context], Union[bool, Coroutine[None, None, bool]]\n    ]\n) -> Callable\n

Allows you to register checks in modules.

@niobot.command()\n@niobot.check(my_check_func)\nasync def my_command(ctx: niobot.Context):\n    pass\n

Parameters:

Name Type Description Default function Callable[[Context], Union[bool, Coroutine[None, None, bool]]]

The function to register as a check

required

Returns:

Type Description Callable

The decorated function.

"},{"location":"reference/commands/#niobot.commands.event","title":"event","text":"
event(name: Optional[Union[str, Event]] = None) -> Callable\n

Allows you to register event listeners in modules.

Parameters:

Name Type Description Default name Optional[Union[str, Event]]

the name of the event (no on_ prefix)

None

Returns:

Type Description Callable"},{"location":"reference/context/","title":"Context","text":"

For each command invoked, the first argument is always a Context instance, which holds a lot of metadata, and a few utility functions to help you write commands.

A lot of the time, these are the three main attributes you'll be using:

  • Context.room (nio.MatrixRoom) - the room the command was invoked in.
  • Context.message (nio.RoomMessageText) - the message that invoked this command.
  • Context.respond - a utility class to help you respond to the command.
"},{"location":"reference/context/#command-context","title":"Command Context","text":"

Event-based context for a command callback

"},{"location":"reference/context/#niobot.context.Context.room","title":"room property","text":"
room: MatrixRoom\n

The room that the event was dispatched in

"},{"location":"reference/context/#niobot.context.Context.client","title":"client property","text":"
client: 'NioBot'\n

The current instance of the client

"},{"location":"reference/context/#niobot.context.Context.command","title":"command property","text":"
command: 'Command'\n

The current command being invoked

"},{"location":"reference/context/#niobot.context.Context.args","title":"args property","text":"
args: list[str]\n

Each argument given to this command

"},{"location":"reference/context/#niobot.context.Context.message","title":"message property","text":"
message: RoomMessageText\n

The current message

"},{"location":"reference/context/#niobot.context.Context.original_response","title":"original_response property","text":"
original_response: Optional[RoomSendResponse]\n

The result of Context.reply(), if it exists.

"},{"location":"reference/context/#niobot.context.Context.latency","title":"latency property","text":"
latency: float\n

Returns the current event's latency in milliseconds.

"},{"location":"reference/context/#niobot.context.Context.respond","title":"respond async","text":"
respond(\n    content: Optional[str] = None,\n    file: Optional[BaseAttachment] = None,\n    reply_to: Optional[Union[RoomMessageText, str]] = None,\n    message_type: Optional[str] = None,\n    *,\n    content_type: Literal[\n        \"plain\", \"markdown\", \"html\", \"html.raw\"\n    ] = \"markdown\",\n    override: Optional[dict] = None,\n    mentions: Union[\"Mentions\", Literal[False], None] = None\n) -> ContextualResponse\n

Responds to the current event.

See niobot.NioBot.send_message for more information.

"},{"location":"reference/context/#contextual-response","title":"Contextual Response","text":"

Context class for managing replies.

Usage of this function is not required, however it is a useful utility.

"},{"location":"reference/context/#niobot.context.ContextualResponse.original_event","title":"original_event async","text":"
original_event() -> Optional[RoomMessage]\n

Fetches the current event for this response

"},{"location":"reference/context/#niobot.context.ContextualResponse.reply","title":"reply async","text":"
reply(\n    content: Optional[str] = None,\n    file: Optional[BaseAttachment] = None,\n    message_type: Optional[str] = None,\n    *,\n    content_type: Literal[\n        \"plain\", \"markdown\", \"html\", \"html.raw\"\n    ] = \"markdown\",\n    override: Optional[dict] = None\n) -> \"ContextualResponse\"\n

Replies to the current response.

This does NOT reply to the original invoking message.

See niobot.NioBot.send_message for more information.

"},{"location":"reference/context/#niobot.context.ContextualResponse.edit","title":"edit async","text":"
edit(\n    content: str,\n    *,\n    message_type: Optional[str] = None,\n    content_type: Literal[\n        \"plain\", \"markdown\", \"html\", \"html.raw\"\n    ] = \"markdown\",\n    override: Optional[dict] = None\n) -> \"ContextualResponse\"\n

Edits the current response.

See niobot.NioBot.edit_message for more information.

"},{"location":"reference/context/#niobot.context.ContextualResponse.delete","title":"delete async","text":"
delete(reason: Optional[str] = None) -> None\n

Redacts the current response.

Parameters:

Name Type Description Default reason Optional[str]

An optional reason for the redaction

None

Returns:

Type Description None

None, as there will be no more response.

"},{"location":"reference/events/","title":"Event Reference","text":""},{"location":"reference/events/#a-little-note-about-event-names","title":"A little note about event names","text":"

Event names are never prefixed with on_, so make sure you're listening to events like message, not on_message!

While trying to listen for an on_ prefixed event will still work, it will throw warnings in the console, and may be deprecated in the future.

"},{"location":"reference/events/#niobot-specific-events","title":"NioBot-specific events","text":"

There are two types of events in niobot: those that are dispatched by niobot itself, and those that're dispatched by matrix-nio. In order to keep backwards compatability, as well as high flexibility and extensibility, niobot's NioBot class actually subclasses nio.AsyncClient. This means that anything you can do with matrix-nio, you can do with niobot.

However, for simplicity, niobot dispatches its own events independently of matrix-nio. These events are listed below.

You can listen to these events with niobot.NioBot.on_event.

Example
import niobot\n\nbot = niobot.NioBot(...)\n\n\n@bot.on_event(\"ready\")\nasync def on_ready(result):\n    print(\"Bot is ready!\")\n    print(\"Logged in as:\", bot.user_id)\n\n\nbot.run(...)\n
"},{"location":"reference/events/#niobot._event_stubs.event_loop_running","title":"event_loop_running async","text":"
event_loop_running() -> Optional[Any]\n

An event that is fired once the event loop is running.

You should use this event to perform any startup tasks.

This event is fired before the bot logs in, and before the first sync() is performed.

This means that if, for example, you wanted to initialise a database, or make some HTTP request in a module, You can @[nio]bot.event(\"event_loop_running\") do it here.

Initialising a database in a module
import niobot\nimport aiosqlite\n\nclass MyModule(niobot.Module):\n    def __init__(self, bot):\n        super().__init__(bot)\n        self.db = None\n\n    @niobot.event(\"event_loop_running\")\n    async def event_loop_running(self):\n        self.db = await aiosqlite.connect(\"mydb.db\")\n        await self.db.execute(...)\n        await self.db.commit\n
"},{"location":"reference/events/#niobot._event_stubs.ready","title":"ready async","text":"
ready(result: SyncResponse) -> Optional[Any]\n

An event that is fired when the bot's first sync() is completed.

This indicates that the bot successfully logged in, synchronised with the server, and is ready to receive events.

Parameters:

Name Type Description Default result SyncResponse

The response from the sync.

required"},{"location":"reference/events/#niobot._event_stubs.message","title":"message async","text":"
message(\n    room: MatrixRoom, event: RoomMessage\n) -> Optional[Any]\n

An event that is fired when the bot receives a message in a room that it is in.

This event is dispatched before commands are processed, and as such the convenient niobot.Context is unavailable.

Not every message is a text message

As of v1.2.0, the message event is dispatched for every decrypted message type, as such including videos, images, audio, and text. Prior for v1.2.0, this was only dispatched for text messages.

Please check either the type of the event (i.e. isinstance(event, niobot.RoomMessageText)) or the event.source[\"content\"][\"msgtype\"] to determine the type of the message.

Tip

If you want to be able to use the niobot.Context in your event handlers, you should use the command event instead.

Furthermore, if you want more fine-grained control over how commands are parsed and handled, you should override niobot.NioBot.process_message instead of using the message event.

Parameters:

Name Type Description Default room MatrixRoom

The room that the message was received in.

required event RoomMessage

The raw event that triggered the message.

required"},{"location":"reference/events/#niobot._event_stubs.command","title":"command async","text":"
command(ctx: Context) -> Optional[Any]\n

This event is dispatched once a command is finished being prepared, and is about to be invoked.

This event is dispatched after the message event, but before command_complete and command_error.

This event features the original context, which can be used to access the message, the command, and the arguments.

Parameters:

Name Type Description Default ctx Context

The context of the command.

required"},{"location":"reference/events/#niobot._event_stubs.command_complete","title":"command_complete async","text":"
command_complete(\n    ctx: Context, result: Any\n) -> Optional[Any]\n

This event is dispatched after a command has been invoked, and has completed successfully.

This event features the context, which can be used to access the message, the command, and the arguments.

Parameters:

Name Type Description Default ctx Context

The context of the command.

required result Any

The result of the command (the returned value of the callback)

required"},{"location":"reference/events/#niobot._event_stubs.command_error","title":"command_error async","text":"
command_error(\n    ctx: Context, error: CommandError\n) -> Optional[Any]\n

This event is dispatched after a command has been invoked, and has completed with an error.

This event features the context, which can be used to access the message, the command, and the arguments.

Getting the original error

As the error is wrapped in a niobot.CommandError, you can access the original error by accessing the CommandError.original attribute.

@bot.event(\"command_error\")\nasync def on_command_error(ctx, error):\n    original_error = error.original\n    print(\"Error:\", original_error)\n

It is encouraged that you inform the end user about an error that has occurred, as by default the error is simply logged to the console. Don't forget, you've got the whole Context instance - use it!

Parameters:

Name Type Description Default ctx Context

The context of the command.

required error CommandError

The error that was raised.

required"},{"location":"reference/events/#niobot._event_stubs.raw","title":"raw async","text":"
raw(room: MatrixRoom, event: Event) -> Optional[Any]\n

This is a special event that is handled when you directly pass a niobot.Event to on_event.

You cannot listen to this in the traditional sense of \"on_event('name')\" as it is not a named event. But, this extensibility allows you to listen directly for events not covered by the library.

Example

The below code will listen directly for the redaction event and will print out the redaction details.

See the nio events documentation for more details and a list of available events.x

import niobot\n\n@bot.on_event(niobot.RedactionEvent)  # listen for redactions\nasync def on_redaction(room, event):\n    print(f\"{event.sender} redacted {event.redacts} for {event.reason!r} in {room.display_name}\")\n
"},{"location":"reference/events/#matrix-nio-events","title":"matrix-nio events","text":"

See the matrix-nio documentation for the base-library set of events.

Remember, you'll need to use nio.Client.add_event_callback in order to listen to these!

New in v1.2.0

You can now listen to matrix-nio events with niobot.NioBot.on_event! Just pass the raw event type to the decorator, rather than a string.

"},{"location":"reference/exceptions/","title":"Exceptions","text":""},{"location":"reference/exceptions/#niobot.exceptions.NioBotException","title":"NioBotException","text":"

Bases: Exception

Base exception for NioBot.

Warning

In some rare cases, all of exception, response and original may be None.

All other exceptions raised by this library will subclass this exception, so at least all the below are always available:

Attributes:

Name Type Description message Optional[str]

A simple humanised explanation of the issue, if available.

response Optional[ErrorResponse]

The response object from the server, if available.

exception Optional[Union[ErrorResponse, BaseException]]

The exception that was raised, if available.

original Union[ErrorResponse, BaseException, None]

The original response, or exception if response was not available.

"},{"location":"reference/exceptions/#niobot.exceptions.NioBotException.bottom_of_chain","title":"bottom_of_chain","text":"
bottom_of_chain(\n    other: Optional[Union[Exception, ErrorResponse]] = None\n) -> Union[BaseException, ErrorResponse]\n

Recursively checks the original attribute of the exception until it reaches the bottom of the chain.

This function finds you the absolute first exception that was raised.

Parameters:

Name Type Description Default other Optional[Union[Exception, ErrorResponse]]

The other exception to recurse down. If None, defaults to the exception this method is called on.

None

Returns:

Type Description Union[BaseException, ErrorResponse]

The bottom of the chain exception.

"},{"location":"reference/exceptions/#niobot.exceptions.NioBotException.__str__","title":"__str__","text":"
__str__() -> str\n

Returns a human-readable version of the exception.

"},{"location":"reference/exceptions/#niobot.exceptions.NioBotException.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Returns a developer-readable version of the exception.

"},{"location":"reference/exceptions/#niobot.exceptions.GenericMatrixError","title":"GenericMatrixError","text":"

Bases: NioBotException

Exception for generic matrix errors where a valid response was expected, but got an ErrorResponse instead.

"},{"location":"reference/exceptions/#niobot.exceptions.MessageException","title":"MessageException","text":"

Bases: NioBotException

Exception for message-related errors.

"},{"location":"reference/exceptions/#niobot.exceptions.LoginException","title":"LoginException","text":"

Bases: NioBotException

Exception for login-related errors.

"},{"location":"reference/exceptions/#niobot.exceptions.MediaException","title":"MediaException","text":"

Bases: MessageException

Exception for media-related errors.

"},{"location":"reference/exceptions/#niobot.exceptions.MediaUploadException","title":"MediaUploadException","text":"

Bases: MediaException

Exception for media-uploading related errors

"},{"location":"reference/exceptions/#niobot.exceptions.MediaDownloadException","title":"MediaDownloadException","text":"

Bases: MediaException

Exception for media-downloading related errors

"},{"location":"reference/exceptions/#niobot.exceptions.MediaCodecWarning","title":"MediaCodecWarning","text":"

Bases: ResourceWarning

Warning that is dispatched when a media file is not in a supported codec.

You can filter this warning by using warnings.filterwarnings(\"ignore\", category=niobot.MediaCodecWarning)

Often times, matrix clients are web-based, so they're limited to what the browser can display. This is usually:

  • h264/vp8/vp9/av1/theora video
  • aac/opus/vorbis/mp3/pcm_* audio
  • jpg/png/webp/avif/gif images
"},{"location":"reference/exceptions/#niobot.exceptions.MetadataDetectionException","title":"MetadataDetectionException","text":"

Bases: MediaException

Exception raised when metadata detection fails. Most of the time, this is an ffmpeg-related error

"},{"location":"reference/exceptions/#niobot.exceptions.CommandError","title":"CommandError","text":"

Bases: NioBotException

Exception subclass for all command invocation related errors.

"},{"location":"reference/exceptions/#niobot.exceptions.CommandNotFoundError","title":"CommandNotFoundError","text":"

Bases: CommandError

Exception raised when a command is not found.

"},{"location":"reference/exceptions/#niobot.exceptions.CommandPreparationError","title":"CommandPreparationError","text":"

Bases: CommandError

Exception subclass for errors raised while preparing a command for execution.

"},{"location":"reference/exceptions/#niobot.exceptions.CommandDisabledError","title":"CommandDisabledError","text":"

Bases: CommandPreparationError

Exception raised when a command is disabled.

"},{"location":"reference/exceptions/#niobot.exceptions.CommandArgumentsError","title":"CommandArgumentsError","text":"

Bases: CommandPreparationError

Exception subclass for command argument related errors.

"},{"location":"reference/exceptions/#niobot.exceptions.CommandParserError","title":"CommandParserError","text":"

Bases: CommandArgumentsError

Exception raised when there is an error parsing arguments.

"},{"location":"reference/exceptions/#niobot.exceptions.CheckFailure","title":"CheckFailure","text":"

Bases: CommandPreparationError

Exception raised when a generic check call fails.

You should prefer one of the subclass errors over this generic one, or a custom subclass.

CheckFailure is often raised by the built-in checker when a check returns a falsy value without raising an error.

"},{"location":"reference/exceptions/#niobot.exceptions.NotOwner","title":"NotOwner","text":"

Bases: CheckFailure

Exception raised when the command invoker is not the owner of the bot.

"},{"location":"reference/exceptions/#niobot.exceptions.InsufficientPower","title":"InsufficientPower","text":"

Bases: CheckFailure

Exception raised when the command invoker does not have enough power to run the command.

"},{"location":"reference/exceptions/#niobot.exceptions.NotADirectRoom","title":"NotADirectRoom","text":"

Bases: CheckFailure

Exception raised when the current room is not m.direct (a DM room)

"},{"location":"reference/utils/checks/","title":"Checks","text":"

There are a few built in checks that you can make use of:

"},{"location":"reference/utils/checks/#niobot.utils.checks.is_owner","title":"is_owner","text":"
is_owner(*extra_owner_ids)\n

Requires the sender owns the bot ([NioBot.owner_id][]), or is in extra_owner_ids.

Parameters:

Name Type Description Default extra_owner_ids

A set of @localpart:homeserver.tld strings to check against.

()

Returns:

Type Description

True - the check passed.

Raises:

Type Description NotOwner

The sender is not the owner of the bot and is not in the given IDs.

"},{"location":"reference/utils/checks/#niobot.utils.checks.is_dm","title":"is_dm","text":"
is_dm(allow_dual_membership: bool = False)\n

Requires that the current room is a DM with the sender.

Parameters:

Name Type Description Default allow_dual_membership bool

Whether to allow regular rooms, but only with the client and sender as members.

False

Returns:

Type Description"},{"location":"reference/utils/checks/#niobot.utils.checks.sender_has_power","title":"sender_has_power","text":"
sender_has_power(\n    level: int, room_creator_bypass: bool = False\n)\n

Requires that the sender has a certain power level in the current room before running the command.

Parameters:

Name Type Description Default level int

The minimum power level

required room_creator_bypass bool

If the room creator should bypass the check and always be allowed, regardless of level.

False

Returns:

Type Description"},{"location":"reference/utils/checks/#niobot.utils.checks.client_has_power","title":"client_has_power","text":"
client_has_power(level: int)\n

Requires that the bot has a certain power level in the current room before running the command.

Parameters:

Name Type Description Default level int

The minimum power level

required

Returns:

Type Description"},{"location":"reference/utils/checks/#niobot.utils.checks.from_homeserver","title":"from_homeserver","text":"
from_homeserver(*homeservers: str)\n

Requires that the sender is from one of the given homeservers.

Parameters:

Name Type Description Default homeservers str

The homeservers to allowlist.

()

Returns:

Type Description"},{"location":"reference/utils/federation/","title":"Federation","text":"

There isn't a lot here aside from a homeserver resolver. A lot of the federation is already handled by matrix-nio itself, so there isn't a lot of need for federation-specific utilities.

"},{"location":"reference/utils/federation/#niobot.utils.federation.resolve_homeserver","title":"resolve_homeserver async","text":"
resolve_homeserver(domain: str) -> str\n

Resolves a given homeserver part to the actual homeserver

Parameters:

Name Type Description Default domain str

The domain to crawl

required

Returns:

Type Description str

The resolved homeserver

"},{"location":"reference/utils/help_command/","title":"The help command","text":"

NioBot comes with a built-in help command, which can be used to display information about other commands.

This built-in command is simple, slick, and most importantly, helpful. It takes one optional argument, command, which changes the output to display information about a specific command.

"},{"location":"reference/utils/help_command/#the-command-list","title":"The command list","text":"

If a command name is not passed to the help command, it will instead display a list of all available commands. The information that will be displayed will be:

  • The command's name
  • Any aliases the command has
  • The command's short description (usually first 100 characters of first line of the command's callback docstring)
  • Any arguments that're required or optional (required are encased in <brackets>, optional in [brackets])

The command is only listed if:

  • The command is not disabled (i.e. disabled=True is passed, or omitted entirely)
  • The command is not hidden (i.e. hidden=True is not passed (or is ommitted entirely))
  • The user passes all of the checks for the command

The command list is sorted alphabetically by command name, and is not paginated or seperated at all. If you want a pretty help command, you should write your own - the default one is just meant to be a happy middle ground between pretty and functional. See the next section for more information on how to do this.

"},{"location":"reference/utils/help_command/#registering-your-own-help-command","title":"Registering your own help command","text":"

If you would like to register your own help command, you need to be aware of the following:

  • The help command is a command, much like any other command, and is registered as such. You should be aware of aliases, case sensitivity, command states (e.g. disabled/enabled) and visibility (hidden/shown), checks, etc.
  • A help command is almost always a user's first impression of your bot. You should make sure that it works 100% of the time, is insanely simple to use, and is very helpful. A help command that just says \"You can use command like ?info\" is not helpful at all, and will likely turn many users away.

As of v1.2.0, the help command is now a class that you can easily subclass. This is the recommended way of doing this.

The only function that you NEED to change is respond, which is the function that is called when the help command is run. The rest is, quite literally, just dectoration.

Here's an example of a custom help command:

from niobot import DefaultHelpCommand, NioBot\n\n\nclass MyHelpCommand(DefaultHelpCommand):\n    async def respond(self, ctx, command=None):\n        if command is None:\n            # No argument was given to !help\n            await ctx.respond(\"This is a custom help command!\")\n        else:\n            # a command name was given to !help\n            await ctx.respond(f\"Help for command {command} goes here!\")\n\n\nclient = NioBot(help_command=MyHelpCommand().respond)\n

Now, when someone runs !help, they will get a custom response from the MyHelpCommand class.

help_command should be a full Command instance.

While the above code gives the response function to the help_command parameter, it is not the ideal way to do this. You should pass a niobot.Command instance to the help command instead, as this gives you a more consistent experience, with fine-grained control over the command's state, aliases, etc.

For the sake of berevity, the above code is used to demonstrate the concept of a custom help command.

"},{"location":"reference/utils/help_command/#the-defaulthelpcommand-class","title":"The DefaultHelpCommand class","text":"

The default help command for NioBot.

This is a very basic help command which lists available commands, their arguments, and a short descrption, and allows for further information by specifying the command name as an argument.

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.clean_output","title":"clean_output staticmethod","text":"
clean_output(\n    text: str,\n    *,\n    escape_user_mentions: bool = True,\n    escape_room_mentions: bool = True,\n    escape_room_references: bool = False,\n    escape_all_periods: bool = False,\n    escape_all_at_signs: bool = False,\n    escape_method: Optional[Callable[[str], str]] = None\n) -> str\n

Escapes given text and sanitises it, ready for outputting to the user.

This should always be used when echoing any sort of user-provided content, as we all know there will be some annoying troll who will just go @room for no apparent reason every 30 seconds.

Do not rely on this!

This function is not guaranteed to escape all possible mentions, and should not be relied upon to do so. It is only meant to be used as a convenience function for simple commands.

Parameters:

Name Type Description Default text str

The text to sanitise

required escape_user_mentions bool

Escape all @user:homeserver.tld mentions

True escape_room_mentions bool

Escape all @room mentions

True escape_room_references bool

Escape all #room:homeserver.tld references

False escape_all_periods bool

Escape all literal . characters (can be used to escape all links)

False escape_all_at_signs bool

Escape all literal @ characters (can be used to escape all mentions)

False escape_method Optional[Callable[[str], str]]

A custom escape method to use instead of the built-in one (which just wraps characters in \\u200b)

None

Returns:

Type Description str

The cleaned text

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.format_command_name","title":"format_command_name staticmethod","text":"
format_command_name(command: Command) -> str\n

Formats the command name with its aliases if applicable

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.format_command_line","title":"format_command_line","text":"
format_command_line(prefix: str, command: Command) -> str\n

Formats a command line, including name(s) & usage.

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.get_short_description","title":"get_short_description staticmethod","text":"
get_short_description(command: Command) -> str\n

Generates a short (<100 characters) help description for a command.

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.get_long_description","title":"get_long_description staticmethod","text":"
get_long_description(command: Command) -> str\n

Gets the full help text for a command.

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.get_default_help","title":"get_default_help async","text":"
get_default_help(\n    ctx: Context, command_name: str = None\n) -> str\n

Gets the default help text

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.respond","title":"respond async","text":"
respond(ctx: Context, command_name: str = None) -> None\n

Displays help information about available commands

"},{"location":"reference/utils/mentions/","title":"Mentions","text":"

New in v1.2.0

This module was added in v1.2.0, so you will not be able to use this if you are using an older version of nio-bot.

See the changelog for more information.

Starting in v1.2.0, nio-bot now has the added functionality of intentional mentions, which allows you to even more finely tune who is mentioned by your messages.

Previously just including @mxid:homeserver.example or @room would create mentions, but sometimes this was undesirable (for example, echoing user input (WHICH YOU SHOULD NOT DO)).

Using Mentions, you can now control exactly how you want mentions to be created.

"},{"location":"reference/utils/mentions/#niobot.utils.mentions.Mentions","title":"Mentions","text":"

Controls the mentions of a sent event.

See: https://spec.matrix.org/v1.11/client-server-api/#user-and-room-mentions

Parameters:

Name Type Description Default room bool

Whether this event mentions the entire room

False user_ids str

List of user IDs mentioned in the event

()"},{"location":"reference/utils/mentions/#niobot.utils.mentions.Mentions.room","title":"room instance-attribute","text":"
room: bool = room\n

Whether this event mentions the entire room

"},{"location":"reference/utils/mentions/#niobot.utils.mentions.Mentions.user_ids","title":"user_ids instance-attribute","text":"
user_ids: List[str] = list(user_ids)\n

List of user IDs mentioned in the event

"},{"location":"reference/utils/mentions/#niobot.utils.mentions.Mentions.as_body","title":"as_body","text":"
as_body() -> Dict[str, Dict[str, Union[bool, List[str]]]]\n

Returns the mentions object as a body dict (e.g. {m.mentions: {room: true, user_ids: []}})

"},{"location":"reference/utils/mentions/#niobot.utils.mentions.Mentions.from_body","title":"from_body classmethod","text":"
from_body(body: dict) -> Mentions\n

Creates a mentions object from a body dict

"},{"location":"reference/utils/mentions/#niobot.utils.mentions.Mentions.add_user","title":"add_user","text":"
add_user(mxid: str) -> Mentions\n

Adds a user to the mentions list

"},{"location":"reference/utils/mentions/#example","title":"Example","text":"
from niobot import NioBot, Context, Mentions\n\n\nbot = NioBot(\n    homeserver=\"https://matrix-client.matrix.org\",\n    user_id=\"@my_user:matrix.org\",\n    command_prefix=\"!\"\n)\n\n@bot.command(\"mention\")\nasync def mention_command(ctx: Context, ping_room: bool = False):\n    \"\"\"Mentions a user.\"\"\"\n    mentions = Mentions()\n    mentions.add_user(ctx.message.sender)\n    if ping_room:\n        mentions.room = True\n    # can also be constructed as `mentions = Mentions(true, ctx.message.sender)\n    content = f\"Hello {ctx.message.sender}, from @room!\"\n    await ctx.respond(\"Hello, \" + ctx.message.sender, mentions=mentions)\n    # This message will ping ctx.message.sender. If `ping_room` is `True`, it will also ping the room, otherwise,\n    # it will only render it.\n
"},{"location":"reference/utils/mentions/#how-automatic-parsing-works","title":"How automatic parsing works","text":"

As this is a feature that may be unexpected to some users, nio-bot will automatically parse mentions if:

  1. NioBot.default_parse_mentions is True (default) AND
  2. NioBot.send_message is not given an explicit mentions= argument AND
  3. The message has a content that is not empty.

In this case, niobot will scan through the message, enable the @room ping if that string is detected in the string, and will attempt to match any user mentions in the message. This is not foolproof, and the best way to ensure mentions are parsed correctly is to manually pass the mentions you want to the Mentions object.

"},{"location":"reference/utils/mentions/#disabling-mentions","title":"Disabling mentions","text":"

If you want to send a message that contains @mentions, but don't want them to actually mention anyone, you can pass mentions=niobot.Mentions() in send_message. This will still render the mentions on the client (usually), but rest assured it did not actually mention them (i.e. they will not have received a notification).

You cannot create a mention that is not also rendered

To mention someone, you must include that in your textual body too, not just in the mentions object. If you only mention someone via the Mentions object, it will not work.

"},{"location":"reference/utils/parsers/","title":"Parsers","text":"

These are a handful of built-in parsers that you can use with niobot.Argument.

How do I use these?

To use a parser, you simply pass parser=<function> when creating Argument(). For example:

from niobot import Argument, command, NioBot\nfrom niobot.utils.parsers import float_parser\n\nbot = NioBot(...)\n\n@bot.command(\n    name=\"sum\", \n    arguments=[\n        Argument(\"num1\", parser=float_parser),\n        Argument(\"num2\", parser=float_parser)\n    ]\n)\nasync def add_numbers(ctx: Context, num1: float, num2: float):\n    await ctx.respond(\"{!s} + {!s} = {!s}\".format(num1, num2, num1 + num2))\n\nbot.run(...)\n

While this is roughly equivalent to Argument(\"num1\", type=float), it can be helpful in cases like json_parser where you need to parse complex types.

Tip

You can also create your own parsers! See Creating Parsers for more information.

This utility modules contains a handful of simple off-the-shelf parser for some basic python types.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.Parser","title":"Parser","text":"

Bases: ABC

A base model class for parsers.

This ABC defines one uniform method, which is __call__, which takes a Context instance, Argument instance, and the user-provided string value.

This parser is designed to be instantiated, and then called with the above arguments. If you want to make a simple parser that does not take additional configuration, it is recommended to use StatelessParser instead.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.StatelessParser","title":"StatelessParser","text":"

Bases: Parser, ABC

A parser base that will not be instantiated, but rather called directly.

This is useful for parsers that do not take any configuration (such as the simple BooleanParser), where a simple one-off call is enough.

Traditionally, you'd call a Parser like this:

parser = Parser(my_argument=True)\nresult = parser(ctx, arg, value)\n# or, in one line\nresult = Parser(my_argument=True)(ctx, arg, value)\n

However, for some simple parsers, there's no need to instantiate them. Instead, you can call them directly. The StatelessParser ABC adds the parse classmethod, meaning you can simply do the following:

result = Parser.parse(ctx, arg, value)\n
Which is just a shortand for the above one-liner. This offers little to no performance benefit, however can make code look cleaner.

As this ABC subclasses the regular Parser, you can still use the traditional instantiation+call method.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.StatelessParser.parse","title":"parse classmethod","text":"
parse(\n    ctx: Context, arg: Argument, value: str\n) -> Optional[Any]\n

Parses the given value using this parser without needing to call __init__() first.

Parameters:

Name Type Description Default ctx Context

The context instance

required arg Argument

The argument instance

required value str

The value to parse

required

Returns:

Type Description typing.Optional[typing.Any]

The parsed value

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.BooleanParser","title":"BooleanParser","text":"

Bases: StatelessParser

Converts a given string into a boolean. Value is casefolded before being parsed.

The following resolves to true: * 1, y, yes, true, on

The following resolves to false: * 0, n, no, false, off

The following will raise a command argument error: anything else

Returns:

Type Description bool

A parsed boolean

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.FloatParser","title":"FloatParser","text":"

Bases: StatelessParser

Converts a given string into a floating point number.

Returns:

Type Description float

A parsed floating point number

Raises:

Type Description CommandParserError

if the value is not a valid number.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.IntegerParser","title":"IntegerParser","text":"

Bases: Parser

Parses an integer, or optionally a real number.

Parameters:

Name Type Description Default allow_floats bool

Whether to simply defer non-explicit-integer values to the float parser. This results in the return type being float

False base int

The base to parse the integer in. Defaults to 10 (denary). 2 is Binary, and 16 is Hexadecimal.

10

Returns:

Type Description Union[int, float]

A parsed integer or float, depending on input & allow_floats

Raises:

Type Description CommandParserError

if the value is not a valid number.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.JSONParser","title":"JSONParser","text":"

Bases: StatelessParser

Converts a given string into a JSON object.

Performance boost

If you want this to be fast, you should install orjson. It is a drop-in replacement for the standard library. While the parser will still work without it, it may be slower, especially for larger payloads.

Returns:

Type Description Union[dict, list, str, int, float, None, bool]

The parsed JSON object

Raises:

Type Description CommandParserError

if the value is not a valid JSON object.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.RoomParser","title":"RoomParser","text":"

Bases: StatelessParser

Parses a room ID, alias, or matrix.to link into a MatrixRoom object.

This parser is async

This parser is async, and should be awaited when used manually.

Returns:

Type Description nio.MatrixRoom

The parsed room instance

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.EventParser","title":"EventParser","text":"

Bases: Parser

Parses an event reference from either its ID, or matrix.to link.

Parameters:

Name Type Description Default event_type Optional[str]

The event type to expect (such as m.room.message). If None, any event type is allowed.

None

Returns:

Type Description typing.Coroutine

The actual internal (async) parser.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.MatrixDotToParser","title":"MatrixDotToParser","text":"

Bases: Parser

Converts a matrix.to link into a MatrixRoomLink namedtuple, which consists of the room, event, and any query passed to the URL.

Parameters:

Name Type Description Default domain str

The domain to check for. Defaults to matrix.to, consistent with average client behaviour.

'matrix.to' require_room bool

Whether to require the room part of this url to be present

True require_event bool

Whether to require the event part of this url to be present

False allow_user_as_room bool

Whether to allow user links as room links

True stateless bool

If true, the link will only be parsed, not resolved. This means rooms will stay as their IDs, etc.

False

Returns:

Type Description typing.Coroutine

The actual internal (async) parser.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.MXCParser","title":"MXCParser","text":"

Bases: StatelessParser

Parses an MXC URL into a MatrixMXCUrl namedtuple, which consists of the server and media ID.

Returns:

Type Description MatrixMXCUrl (namedtuple)

The parsed MXC URL

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.MatrixUserParser","title":"MatrixUserParser","text":"

Bases: StatelessParser

Parses a string into a MatrixUser instance from matrix-nio.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.boolean_parser","title":"boolean_parser","text":"
boolean_parser(*args, **kwargs)\n

Deprecated boolean parser. Please use niobot.utils.parsers.BooleanParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.float_parser","title":"float_parser","text":"
float_parser(*args, **kwargs)\n

Deprecated float parser. Please use niobot.utils.parsers.FloatParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.integer_parser","title":"integer_parser","text":"
integer_parser(allow_floats: bool = False, base: int = 10)\n

Deprecated integer parser. Please use niobot.utils.parsers.IntegerParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.json_parser","title":"json_parser","text":"
json_parser(*args, **kwargs)\n

Deprecated integer parser. Please use niobot.utils.parsers.JSONParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.room_parser","title":"room_parser","text":"
room_parser(*args, **kwargs)\n

Deprecated room parser. Please use niobot.utils.parsers.RoomParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.event_parser","title":"event_parser","text":"
event_parser(event_type: Optional[str] = None)\n

Deprecated event parser. Please use niobot.utils.parsers.EventParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.matrix_to_parser","title":"matrix_to_parser","text":"
matrix_to_parser(\n    require_room: bool = True,\n    require_event: bool = False,\n    allow_user_as_room: bool = True,\n)\n

Deprecated matrix.to parser. Please use niobot.utils.parsers.MatrixDotToParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.mxc_parser","title":"mxc_parser","text":"
mxc_parser(*args, **kwargs)\n

Deprecated MXC parser. Please use niobot.utils.parsers.MXCParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#creating-parsers","title":"Creating Parsers","text":"The old way (pre-1.1.0)

Creating your own parser is actually really easy. All the library needs from you is a function that:

  • Takes niobot.Context as its first argument
  • Takes niobot.Argument as its second argument
  • Takes a string (the user's input) as its third argument
  • Returns a sensible value
  • Or, raises CommandArgumentsError with a helpful error message.

Do all of this, and you can very easily just pass this to Argument!

For example, if you wanted to take a datetime, you could write your own parser like this:

from datetime import datetime\nfrom niobot import Argument, command, NioBot\n\n\ndef datetime_parser(ctx: Context, arg: Argument, user_input: str):\n    try:\n        return datetime.strptime(user_input, \"%Y-%m-%d %H:%M:%S\")\n    except ValueError:\n        raise CommandArgumentsError(\"Invalid datetime format. Expected YYYY-MM-DD HH:MM:SS\")\n\nbot = NioBot(...)\n\n\n@bot.command(name=\"remindme\", arguments=[Argument(\"time\", arg_type=datetime, parser=datetime_parser)])\nasync def remind_me(ctx: Context, time: datetime):\n    await ctx.respond(\"I'll remind you at {}!\".format(time.strftime(\"%c\")))\n\nbot.run(...)\n

Creating custom parsers for nio-bot is really simple. All you need to do is subclass either Parser or StatelessParser and implement the parse method.

However, if you want some detailed information, seek the guide

"},{"location":"reference/utils/string_view/","title":"String View","text":"

This is mostly an internal utility.

The ArgumentView is mostly used by the internal command parser to parse arguments. While you will be able to use this yourself, its very unlikely that you'll ever actually need it.

This is a work in progress.

The string view does a lot of complicated maths and logic to determine arguments. It's not as simple as just splitting the string on every whitespace and calling it an argument, the ArgumentView parser has to check for quotes, escape characters, and more.

Due to the complexity of the parser, it's very likely that there are still bugs in the parser. Fixes welcome!

"},{"location":"reference/utils/string_view/#niobot.utils.string_view.ArgumentView","title":"ArgumentView","text":"

A parser designed to allow for multi-word arguments and quotes

For example, the arguments 1 \"2 3\" 4 would result in three items in the internal list: 1, 2 3, and 4

This is most useful when parsing arguments from a command, as it allows for multi-word arguments.

Parameters:

Name Type Description Default string str

The string to parse

required"},{"location":"reference/utils/string_view/#niobot.utils.string_view.ArgumentView.eof","title":"eof property","text":"
eof: bool\n

Returns whether the parser has reached the end of the string

Returns:

Type Description bool

Whether the parser has reached the end of the string (cursor is greater than or equal to the length of the string)

"},{"location":"reference/utils/string_view/#niobot.utils.string_view.ArgumentView.add_arg","title":"add_arg","text":"
add_arg(argument: str) -> None\n

Adds an argument to the argument list

Parameters:

Name Type Description Default argument str

The argument to add

required

Returns:

Type Description None

none

"},{"location":"reference/utils/string_view/#niobot.utils.string_view.ArgumentView.parse_arguments","title":"parse_arguments","text":"
parse_arguments() -> ArgumentView\n

Main parsing engine.

Returns:

Type Description ArgumentView

self

"},{"location":"reference/utils/typing/","title":"Typing helper","text":"

This utility module contains one tool: the Typing class. It is internally used in the <send/edit/delete>_message functions of NioBot, but you can use it at any point to send typing events to the chat.

"},{"location":"reference/utils/typing/#usage","title":"Usage","text":"

Context manager to manage typing notifications.

Parameters:

Name Type Description Default client NioBot

The NioBot instance

required room_id str

The room id to send the typing notification to

required timeout int

The timeout in seconds

30 persistent bool

Whether to send a typing notification every timeout seconds, to keep the typing status active

True

Warning

Nesting Typing instances for one specific room is a bad idea, as when each instance is exited, it stops typing for the given room. For example, the below will not work as expected:

from niobot import NioBot, utils\n\nbot = NioBot(...)\n\n@bot.command()\nasync def ping(ctx):\n    async with utils.Typing(ctx.client, ctx.room.room_id):\n        await ctx.respond(\"Pong!\")\n\nbot.run(...)\n

This will not work because Context.respond calls NioBot.send_message, and NioBot.send_message creates its own Typing instance. Once ctx.respond returns, the internal Typing instance is destroyed, and the typing event is stopped, as is the behaviour of exiting the context manager. This means that either if on the loop, the upper-most utils.Typing instance will simply just create a new typing notification, or will not (especially if persistent was set to False). This breaks the whole persistence of typing.

If you want to use Typing to show that you're processing something:

If you want to use Typing to show a user that your bot is \"thinking\", or similar, you should make sure you exit the instance before responding. For example:

from niobot import NioBot, Typing\nimport httpx\n\nbot = NioBot(...)\n\n@bot.command()\nasync def process(ctx):\n    \"\"\"Spends a worryingly long time making a network request.\"\"\"\n    async with Typing(ctx.client, ctx.room.room_id):\n        await httpx.get(\"https://example.com\")\n    await ctx.respond(\"Done!\")\n

Be aware that this will cause a momentary blip in the xyz is typing status, but this is unavoidable, simply due to the semi-stateless nature of this context wrapper

A potential future solution would be to implement some funky internal lock mechanism and/or just prevent nested Typing instances, but this is not a priority at the moment.

"},{"location":"reference/utils/typing/#niobot.utils.Typing.__aenter__","title":"__aenter__ async","text":"
__aenter__()\n

Starts the typing notification loop, or sends a single typing notification if not persistent.

"},{"location":"reference/utils/typing/#niobot.utils.Typing.__aexit__","title":"__aexit__ async","text":"
__aexit__(exc_type, exc, tb)\n

Cancels any existing typing loop under this instance and sends a typing notification to stop typing.

"},{"location":"reference/utils/unblock/","title":"Unblock","text":"

A common problem developers encounter when working with an asyncio event loop is long blocking code. This can be caused by a number of things, but the most common is a call to a library that is not async-aware, and has many blocking operations (such as requests, or even the built-in open() + read() functions).

To alleviate this, NioBot provides an \"unblock\" utility, which is a simple async function that will run any blocking code in the event loop executor, and returns the result, without pausing the event loop. This is equivalent to loop.run_in_executor(None, func, *args, **kwargs).

A good example

from niobot import NioBot, command\nfrom niobot.utils import run_blocking\n\nbot = NioBot(...)\n\n\n@bot.command(name=\"read\")\nasync def read_file(ctx: Context, filename: str):\n    with open(filename, \"r\") as f:\n        contents = await run_blocking(f.read)\n    await ctx.respond(contents)\n\nbot.run(...)\n
This will read the contents of a file, without blocking the event loop, unlike the following code:

A bad example

    from niobot import NioBot, command\n    from niobot.utils import run_blocking\n\n    bot = NioBot(...)\n\n\n    @bot.command(name=\"read\")\n    async def read_file(ctx: Context, filename: str):\n        with open(filename, \"r\") as f:\n            contents = f.read()\n        await ctx.respond(contents)\n\n    bot.run(...)\n
This example is bad because it will prevent any other event processing until f.read() finishes, which is really bad if the file is large, or the disk is slow. For example, if you read at 1mb/s, and you have a 10 megabyte file, you will block the event loop for approximately 10 seconds, which means your program cannot do anything in those ten seconds, and as such your bot will appear to be non-functional!

"},{"location":"reference/utils/unblock/#niobot.utils.unblocking.run_blocking","title":"run_blocking async","text":"
run_blocking(\n    function: Callable[..., T], *args: Any, **kwargs: Any\n) -> T\n

Takes a blocking function and runs it in a thread, returning the result.

You should use this for any long-running functions that may take a long time to respond that are not coroutines that you can await. For example, running a subprocess.

Example
import asyncio\nimport subprocess\nfrom niobot.utils import run_blocking\n\nasync def main():\n    result = await run_blocking(subprocess.run, [\"find\", \"*.py\", \"-type\", \"f\"], capture_output=True)\n    print(result)\n\nasyncio.run(main())\n

Parameters:

Name Type Description Default function Callable[..., T]

The function to call. Make sure you do not call it, just pass it.

required args Any

The arguments to pass to the function.

() kwargs Any

The keyword arguments to pass to the function.

{}

Returns:

Type Description T

The result of the function.

"},{"location":"reference/utils/unblock/#niobot.utils.unblocking.force_await","title":"force_await async","text":"
force_await(\n    function: Union[Callable, Coroutine],\n    *args: Any,\n    **kwargs: Any\n)\n

Takes a function, and if it needs awaiting, it will be awaited. If it is a synchronous function, it runs it in the event loop, preventing it from blocking.

This is equivalent to (pseudo):

if can_await(x):\n    await x\nelse:\n    await run_blocking(x)\n

Parameters:

Name Type Description Default function Union[Callable, Coroutine]

The function to call. Make sure you do not call it, just pass it.

required args Any

The arguments to pass to the function.

() kwargs Any

The keyword arguments to pass to the function.

{}

Returns:

Type Description

The result of the function.

"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Index","text":"

Welcome to the Nio-Bot Documentation

"},{"location":"#what-is-niobot","title":"What is NioBot?","text":"

NioBot (& nio-bot) is a framework built upon the matrix-nio client library, built with the creation of bots in mind. NioBot provides a simple and intuitive way to build bots for the Matrix framework by simplifying a lot of the mundane tasks that come with building a bot.

By making use of nio, you can access the full power of the Matrix protocol, while still being able to build a bot using nio-bot with ease.

Why NioBot, and not just regular nio?

Take for example, the following code:

from niobot import NioBot, Context\n\n\nbot = NioBot(\n    homeserver=\"https://matrix-client.matrix.org\",\n    user_id=\"@my_user:matrix.org\",\n    command_prefix=\"!\"\n)\n\n@bot.command(\"ping\")\nasync def ping_command(ctx: Context):\n    \"\"\"Checks if the bot is online & responding.\"\"\"\n    await ctx.respond(\"Pong!\")\n\n\nbot.run(access_token=\"abc123\")\n

This is an incredibly simple working example of a bot that responds to the command !ping with Pong!. You simply run this with python3 bot.py, and you're away!

Here's the same code (not fully, there are a LOT of behind the scenes in niobot) in base nio:

import asyncio\nfrom nio import AsyncClient, MatrixRoom, RoomMessage\n\n\nclient = AsyncClient(\"https://matrix-client.matrix.org\", \"@my_user:matrix.org\")\n\n\nasync def ping_command(room: MatrixRoom, event: RoomMessage):\n    if event.sender == client.user_id:\n        return\n    body = event.body\n    if event.body.startswith(\"!\"):\n        if event.body.split()[0][1:].lower() == \"ping\":\n            await client.room_send(\n                room.room_id,\n                \"m.room.message\",\n                {\n                    \"msgtype\": \"m.notice\",\n                    \"body\": \"Pong!\",\n                    \"m.relates_to\": {\n                        \"m.in_reply_to\": {\n                            \"event_id\": event.event_id\n                        }\n                    }\n                }\n            )\n\n\nclient.add_event_callback(ping_command, (RoomMessage,))\nclient.access_token = \"abc123\"\n\nasyncio.run(client.sync_forever())\n

At first, it doesn't look too difficult right? But, as you start to add more commands, or add more functionality, you'll end up building up more and more boilerplate, and it can get quite messy quite quickly.

This is where nio-bot comes in. By abstracting away a lot of the nuanced functionality in favour of a simplified interface, so that you can focus on building meaningful code, rather than annoying boilerplate.

"},{"location":"#features","title":"Features","text":"

NioBot contains all of the features of matrix-nio, plus a few more:

  • Integrated command system: Easily create commands with a decorater and function. That's all you need.
  • Powerful context: Access all of the metadata you need about a command with the [niobot.Context][] object, given to every command.
  • Robust error handling: Your bot won't crash as soon as an error occurs. Instead, it will be passed on to a handler, and the bot will continue running.
  • Simple configuration: Pass in a few parameters to [niobot.NioBot][], and you're ready to go.
  • Automatic Markdown + HTML rendering: Send messages in markdown or HTML, and NioBot will automatically render them for you.
  • Easy events: Listening for events in NioBot is incredibly similar to listening for commands, and is just as easy.
NioBot does not make an effort to support end-to-end encryption

While NioBot does support end-to-end encryption, it does not make an effort to support it. Making end-to-end encryption work in a headless fashion is incredibly difficult, and while it does work, users have reported that E2EE is unreliable and can unexpectedly break at any time, and is hard to debug.

We do not recommend that you expect E2EE to work when building a bot with NioBot. If it works, that's great! If not, we cannot help.

"},{"location":"#installation","title":"Installation","text":"

You can install NioBot from PyPi:

pip install nio-bot\n# Or, get the latest pre-release\npip install --pre nio-bot\n

or straight from git:

pip install git+https://github.com/nexy7574/nio-bot.git\n
"},{"location":"#contact","title":"Contact","text":"

See the Matrix room for help, or open a GitHub issue for bugs or feature requests.

"},{"location":"changelog/","title":"Changelog","text":""},{"location":"changelog/#unreleased","title":"Unreleased","text":"These changes are not in a stable release.

You can only get these changes by installing the library from GitHub. This is not recommended in production, as these changes are often not properly battle-tested.

However, if you encounter an issue with these changes, you should open an issue on GitHub so that we can release them sooner!

  • Remove force_write properly
  • Fix <instance> has no attribute 'mro' error when initialising auto-detected arguments
  • Fixed niocli get-access-token crashing on windows
  • Fixed NioBot throwing a warning about failing key uploads without logging the actual error
  • Allowed command_prefix to be an iterable, converting single-strings into a single-item iterable too.
  • Changed the event type of the message event to be any nio.RoomMessage, not just Text.
  • Merged xyzamorganblurhash into ImageAttachment
  • Removed deprecated automatic_markdown_parser option and functionality in NioBot
  • Fixed niobot.utils.parsers.EventParser raising an error when used
  • Added beautifulsoup4 as a hard dependency. Backwards compatibility is still kept in case bs4 is not installed.
  • Fixed some typing dotted around the client
  • Removed the deprecated name parameter from niobot checks
  • Added support for passing raw nio.Event types to event listeners
  • Added proper support for typing.Optional in automatic argument detection
  • Added support for *args in automatic argument detection
  • Fixed niobot attachments (Image/Video/Audio) sending null for metadata, which may cause incorrect client behaviours
  • Added niobot.utils.Mentions to handle intentional mentions in messages
  • (Typing) send_message can now reply to any RoomMessage, not just RoomMessageText.
  • niobot.util.help_command.help_command_callback was removed, in line with deprecation.
  • niobot.NioBot.start will now query /_matrix/client/versions to fetch server version metadata.
  • Fix RuntimeError due to concurrent typing in send_message
  • Updated the documentation index page and added documentation for Mentions
  • Fixed the versioned docs deployment
  • Updated the help command
    • Unified all of the functions into a single class, niobot.utils.help_command.DefaultHelpCommand, to make subclassing easier.
    • default_help_command was replaced with DefaultHelpCommand().respond.
    • Help command will no longer display commands in the command list that the current user cannot run
  • Added Command.can_run(ctx), which runs through and makes sure that all of the command checks pass.
  • Added backwards compatibility support for legacy media endpoints (servers that don't support matrix v1.11 yet). Authenticated media will still be used by default.
  • Python 3.13 is now officially supported in time for v1.2.0a2
  • niobot attachment types now support os.PathLike, as well as str, BytesIO, and Pathlib, in case you had some freaky custom path type
  • Overlapping typing events in anything using room_send (e.g. send_message, edit_message) will no-longer throw an error if there is a mid-air collision. Instead, a warning will be logged to the stream, and the operation will be a no-op. This may cause some inconsistencies in the typing indicators sent by nio-bot, however that is preferrable to errors.
  • You can now have a little bit more control over the sync loop
    • niobot.NioBot now allows you to pass a static presence (online, unavailable, offline), False to outright disable presence, and None (default) to set it automatically based on the startup stage (recommended for slower connections)
    • You can now, if you needed to for some reason, disable full state sync via sync_full_state=False.
  • Fixed niobot.NioBot.join throwing a JSON EOF in some cases
  • Added the reason parameter to niobot.NioBot.join and niobot.NioBot.room_leave as optional strings
  • NioBot's auto-join feature now uses this to include a reason when automatically joining rooms
  • Fixed module event handlers, in debug logs, being named as anonymous functions, rather than their true names. This will make debugging issues with your event handlers easier.
  • Removed the password login critical log in favour of simply documenting the dangers of using a password
  • niobot.NioBot.send_message will now automatically parse mentions if not explicitly provided, to take full advantage of intentional mentions.
  • Added force_initial_sync to niobot.NioBot, which will force the bot to sync all rooms before starting the event loop.
  • DM rooms are now removed properly from account data when leaving.
  • Fixed niobot.NioBot.on_event not properly accepting raw nio.Event types
  • Fixed some faulty sync responses triggering commands twice
  • Fixed a bug in the default help command that would display hidden commands regardless.
  • Removed fallback replies in messages (see: MSC2781)
"},{"location":"changelog/#v111-2024-06-26","title":"v1.1.1 (2024-06-26)","text":"
  • Heavy CI improvements (2024-05-08 -> 2024-06-15)
  • Deprecated unimplemented force_write parameter in some BaseAttachment (and subclass) methods. (2024-06-15)
"},{"location":"changelog/#v110post3-2024-04-16","title":"v1.1.0.post3 (2024-04-16)","text":""},{"location":"changelog/#new-features","title":"New features","text":"
  • Added CHANGELOG.md (and consequently, this page) to documentation. (2024-02-08)
  • NioBot._get_id now tells you what object it couldn't identify in the raised exception. (2024-02-11)
  • NioBot.mount_module now warns you if you define a custom setup() that doesn't update the command or event register. (2024-02-11)
"},{"location":"changelog/#v110post2-2024-02-08","title":"v1.1.0.post2 (2024-02-08)","text":""},{"location":"changelog/#new-features_1","title":"New features","text":"
  • Added auto_read_messages key word argument to NioBot to automatically read messages from rooms. Defaults to True. Disabling this (False) will prevent read reciepts from automatically being sent.
"},{"location":"changelog/#bug-fixes","title":"Bug fixes","text":"
  • Fixed NioBot.get_dm_rooms raising a 401 Unauthorised error regardless of any state.
  • Fixed NioBot.get_dm_rooms raising a GenericMatrixError whenever there were no DM rooms, instead of gracefully returning an empty object.
  • Fixed NioBot.get_dm_rooms using outdated code from before matrix-nio==0.24.0.
"},{"location":"changelog/#v110-2024-01-30","title":"v1.1.0 (2024-01-30)","text":"

The license changed in this release.

With release v1.1.0 (specifically commit 421414d), the license for nio-bot was changed from GPLv3 to LGPLv3. In short, this means you do not have to open source your code, and you are able to commercialise your project if you use nio-bot.

This version's changelog includes changes made in its pre-release versions

This changelog includes all changes made since the last stable release, including those made in pre-release versions. If you scroll down, you will see duplicate feature changelogs where the feature was changed in a pre-release version.

"},{"location":"changelog/#new-features_2","title":"New features","text":"
  • Added niobot.Context.invoking_prefix.
  • Python 3.12 is now supported.
  • Added niobot.NioBot.is_ready, which is an asyncio.Event.
  • Added command-level checks (@niobot.check)
  • Added sparse DM room support.
  • Added additional exception types, such as GenericMatrixError.
  • Additional type-hinting for the entire library.
"},{"location":"changelog/#changes","title":"Changes","text":"
  • License changed from GPLv3 to LGPLv3.
  • Improved existing type-hinting.
  • event_id is prioritised over room_id in niobot.NioBot._get_id.
  • niobot was changed to nio-bot (for consistency) throughout documentation and the pip installation guide.
"},{"location":"changelog/#v110b1post1-and-v110b1-2023-10-16","title":"v1.1.0b1.post1 (and v1.1.0b1) (2023-10-16)","text":""},{"location":"changelog/#new-features_3","title":"New features","text":"
  • Added CI testing to the library.
  • Rewrote argument parsers to use a class-based ABC system, rather than a function-based system. See documentation.
  • Added the ignore_self flag to niobot.NioBot, allowing you to choose whether the client will ignore its own messages.
  • Added support for typing.Annotated in commands.
"},{"location":"changelog/#deprecations-removals","title":"Deprecations & Removals","text":"
  • The property niobot.Module.log was fully removed - it was never fully functional and often tripped up users as it was unsettable.
  • The property niobot.Module.client was deprecated - you should use niobot.Module.client instead.
"},{"location":"changelog/#v110a3-2023-10-06","title":"v1.1.0a3 (2023-10-06)","text":""},{"location":"changelog/#changes_1","title":"Changes","text":"
  • Prioritise event_id over room_id for the _get_id function
  • Add Context.invoking_prefix
  • Type hinting and code refactor
"},{"location":"changelog/#v110a2-2023-08-21","title":"v1.1.0a2 (2023-08-21)","text":""},{"location":"changelog/#new-features_4","title":"New features","text":"
  • Backported support to Python 3.9 and Python 3.10.
"},{"location":"changelog/#bug-fixes_1","title":"Bug fixes","text":"
  • Fixed a bug where disabled commands could crash the command parser.
"},{"location":"changelog/#documentation-changes","title":"Documentation changes","text":"
  • Replaced niobot with nio-bot for pip install guide.
  • Fixed PyPi link in README.
  • Cleaned up documentation issues.
  • Removed the examples on GitHub (until the package is more stable in terms of design).
"},{"location":"changelog/#v110a1-2023-07-31","title":"v1.1.0a1 (2023-07-31)","text":""},{"location":"changelog/#new-features_5","title":"New features","text":"
  • Added documentation for events.
  • Added niobot.attachments.which function.
  • Added very early DM room support.
  • Added easier ways to customise the help command.
  • Added more specific exception types.
  • Added event_parser and room_parser
"},{"location":"changelog/#changes_2","title":"Changes","text":"
  • force_await now just awaits coroutines rather than casting them to a task
  • Command arguments now properly raise the correct errors
"},{"location":"changelog/#v102-2023-07-16","title":"v1.0.2 (2023-07-16)","text":"

This is an urgent security release - denial of service vulnerability.

This release fixes a vulnerability where a potentially accidentally crafted message payload could cause the bot to completely crash. If you had disabled ignoring old messages, this could cause a crash loop if your bot automatically restarted.

If you cannot update to v1.0.2 from a previous release, you should implement the following workaround:

import niobot\n\n\nclass PatchedClient(niobot.NioBot):\n    async def process_message(self, *args):\n        try:\n            await super().process_message(*args)\n        except IndexError:  # IndexError is the only error thrown during parsing\n            pass  # or print, whatever\n\n# bot = niobot.NioBot(...)\nbot = PatchedClient(...)  # use your patched version\n
"},{"location":"changelog/#new-features_6","title":"New features","text":"
  • Added niobot.attachments.get_image_metadata (depends on imagemagick)
  • niocli version now shows the OS and CPU architecture.
  • niobot.attachment.* now always imports all attachment types into the niobot namespace, regardless of installed external dependencies.
"},{"location":"changelog/#bug-fixes_2","title":"Bug fixes","text":"
  • Fixed niobot.ImageAttachment being unable to detect image streams.
  • Fixed niobot.BaseAttachment setting incorrect file properties
  • niobot.ImageAttachment no longer explicitly fails upon encountering an unknown format, it simply emits a warning, in line with niobot.VideoAttachment.
  • Fixed an unexpected input causing the entire process to crash.
"},{"location":"changelog/#v101-2023-07-12","title":"v1.0.1 (2023-07-12)","text":"
  • Added stub setup.py for really old pip versions.
  • Updated the README.
"},{"location":"changelog/#v100-2023-07-12","title":"v1.0.0 (2023-07-12)","text":"

The first stable release! This version has many breaking changes since v0.1.0, and as such is not very backwards compatible.

  • MediaAttachment and Thumbnail were split up into ImageAttachment, VideoAttachment, AudioAttachment, and FileAttachment.
  • Attachments now automatically detect metadata.
  • Thumbnailing was massively improved.
  • Blurhashes are automatically generated for images.
  • Attachments now fully support end-to-end encryption.
  • Attachments will now emit warnings when a non web-safe codec is used.
  • Automatic command parameter parsing, so you no longer have to manually specify Command(arguments=[...]).
  • Automatic help command sanitisation.
  • Added additional requirements.
  • Added the ability to add and remove reactions.
  • Added __repr__ to most objects in the library.
  • Added more helper/utility functions.
  • Added documentation (tada!).
  • Added more customisation options to niobot.NioBot.

You've reached the end! There are no previously documented releases before the big 1.0.0. If you want to expand this list, you can contribute on GitHub! Open issues, or even better, make some pull requests. We love new contributors!

"},{"location":"guides/001-getting-started/","title":"Getting started / quick start","text":"

Want to skip to see some examples?

You can see some examples on GitHub

So, you've joined matrix, had a look around, and now you want to make your own little bot? Guess what, you can do just that with nio-bot!

"},{"location":"guides/001-getting-started/#prerequisites","title":"Prerequisites","text":"

You will need the following in general:

  • A matrix account you can log into (username and password initially)

And the following installed on the machine you want to run the bot on:

  • Python with sqlite support
  • libolm (use your system package manager, like apt or pacman) in order to use end-to-end encryption.
  • libmagic (usually you can just install python3-magic) which is required for attachments.
  • A decent network connection (at least a few megabits a second, preferably more)
  • At least 100mb free storage space (for the database and other files)
"},{"location":"guides/001-getting-started/#installation","title":"Installation","text":"

After you've installed and acquired the above, you can install nio-bot with the following command:

python3 -m pip install nio-bot[cli]\n# Note that we install the extras for `cli` here - the niobot CLI comes with a bunch of useful tools we'll use.\n
If you would like to install support for end-to-end encryption, you can install the following instead:
python3 -m pip install nio-bot[cli,e2ee]\n

"},{"location":"guides/001-getting-started/#creating-the-start-of-your-bot","title":"Creating the start of your bot","text":"

In our instance here, we'll create a few files:

  1. A config.py file to store our configuration.
  2. A main.py file to store our bot code.
  3. A fun.py file to store a module (later on).

And you'll need a directory:

  1. store - this is where nio-bot will store its database and other files.
"},{"location":"guides/001-getting-started/#file-structure","title":"File structure","text":"

And as such, our directory structure will look like this:

test-niobot/\n\u251c\u2500\u2500 config.py\n\u251c\u2500\u2500 fun.py\n\u251c\u2500\u2500 requirements.txt\n\u251c\u2500\u2500 store/\n\u2514\u2500\u2500 main.py\n

Danger

Make sure, if you are using version control, to add config.py to your .gitignore file! This file contains all of your personal information, such as your password, and should not be shared with anyone.

While you're at it, you should add the store directory to your .gitignore file as well, as that will contain encryption keys later on.

"},{"location":"guides/001-getting-started/#setting-up-configpy","title":"Setting up config.py","text":"

For this example, we will assume you are using https://matrix.org as your homeserver.

In our config.py file, we'll add the following:

HOMESERVER = \"https://matrix-client.matrix.org\"\nUSER_ID = \"@my-username:matrix.org\"\nPASSWORD = \"my-password\"\n

Warning

Make sure to replace the above with your own homeserver, user ID, and password!

"},{"location":"guides/001-getting-started/#making-the-bot-runtime-file","title":"Making the bot runtime file","text":"

And, to make a simple bot, you can just copy the below template into your main.py file:

import niobot\nimport config\n\nbot = niobot.NioBot(\n    homeserver=config.HOMESERVER,\n    user_id=config.USER_ID,\n    device_id='my-device-id',\n    store_path='./store',\n    command_prefix=\"!\",\n    owner_id=\"@my-matrix-username:matrix.org\"\n)\n# We also want to load `fun.py`'s commands before starting:\nbot.mount_module(\"fun\")  # looks at ./fun.py\n\n@bot.on_event(\"ready\")\nasync def on_ready(_):\n    # That first argument is needed as the first result of the sync loop is passed to ready. Without it, this event\n    # will fail to fire, and will cause a potentially catasrophic failure.\n    print(\"Bot is ready!\")\n\n\n@bot.command()\nasync def ping(ctx):  # can be invoked with \"!ping\"\n    await ctx.respond(\"Pong!\")\n\nbot.run(password=config.PASSWORD)\n
About owner_id

owner_id is intended to tell nio-bot who owns the current instance. Do not set this to be the same thing as config.USER_ID (your bot's ID)! The only time you should do that is if you want to run the bot on the same account you use.

Otherwise, set this to be your own user ID, so that you can use any \"owner only\" commands.

It is not necessary to set this though, so it can be omitted or set to None. Just note that NioBot.is_owner(...) will raise an error when used in that case.

"},{"location":"guides/001-getting-started/#enabling-logging","title":"Enabling logging","text":"

You'll probably find that it's useful to enable debug logging while you're developing your bot. To do that, you can add the following to your main.py file:

import logging\nimport niobot\n\nlogging.basicConfig(level=logging.DEBUG)\n# or to save to a file (uncomment):\n# logging.basicConfig(level=logging.DEBUG, filename=\"bot.log\")\n\nbot = niobot.NioBot(...)\n...\n

This will print an awful lot of text to your console, but will ultimately be helpful for debugging any issues you are encountering, as it will show you a lot of what niobot is doing.

"},{"location":"guides/001-getting-started/#making-funpy","title":"Making fun.py","text":"

Now, fun.py is going to be a module.

Modules are a great way to organize your code, and make it easier to manage. They also allow you to easily add new commands to your bot without having to edit the main file, which means you can split your code up, and make it... modular!

To start, we need to make the fun.py python file, and add the following:

import niobot\n\n\nclass MyFunModule(niobot.Module):  # subclassing niobot.Module is mandatory for auto-detection.\n    def __init__(self, bot):\n        super().__init__(bot)\n        # This gives you access to `self.bot`, which is the NioBot instance you made in main.py!\n
And that's it! You made your module!

"},{"location":"guides/001-getting-started/#but-wait-theres-more","title":"But wait, there's more!","text":"

You may notice that with this being a separate module, you can't use @bot.command, or @bot.on_event, or reference bot at all!

You'd initially assume \"Oh, I'll just import bot from main.\" - but that's not going to work. The reason for this is every time main.py is imported, it creates a new NioBot, and then... calls bot.run() at the end, meaning not only would your import never finish, but it would also cause a massive recursion bug!

The way you get around this is instead with @niobot.command(). This is a decorator that will register the command with the bot, however is designed specifically with modules in mind.

Let's compare the two, for simplicity:

@niobot.NioBot.command() @niobot.command() Adds commands to the register immediately Adds commands to the register once the module is loaded Can only be used at runtime, or wherever bot can be imported from Can only be used in modules (has no effect outside aniobot.Module!) Takes priority over @niobot.command() due to the immediate register Lower priority than NioBot.command() due to the \"lazy loading\"

Do be aware though, both decorators will take the exact same arguments as niobot.Command.

"},{"location":"guides/001-getting-started/#adding-a-command-to-funpy","title":"Adding a command to fun.py","text":"

So, let's add a command to our module:

import niobot\n\n\nclass MyFunModule(niobot.Module):  # subclassing niobot.Module is mandatory for auto-detection.\n    def __init__(self, bot):\n        super().__init__(bot)\n\n    @niobot.command()\n    async def hello(self, ctx: niobot.Context):\n        await ctx.respond(\"Hello %s!\" % ctx.event.sender)\n

This will add a command, !hello, that will reply with \"Hello {@author}!\"

"},{"location":"guides/001-getting-started/#starting-the-bot","title":"Starting the bot","text":"

Hold your horses, you're not quite ready yet!

Generally, it's a terrible idea to always use a password in your code. It's a security risk, and in matrix it can result in creating many sessions, which you don't want, especially if you're using encryption!

"},{"location":"guides/001-getting-started/#getting-an-access-token","title":"Getting an access token","text":"

An access token is like a server-generated long-lived password. You will probably want one in order to repeatedly use the same session, and to avoid having to use your password in your code.

You can get your password with niocli get-access-token. For example:

(venv) [me@host test-niobot]$ niocli get-access-token\nUser ID (@username:homeserver.tld): @test:matrix.org\nPassword (will not echo):\nDevice ID (a memorable display name for this login, such as 'bot-production') [host]:\nResolving homeserver... OK\nGetting access token... OK\nAccess token: syt_<...>\n

What you're going to do now, is copy the full access token string, and open config.py again Now, replace PASSWORD=... with ACCESS_TOKEN=\"syt_<...>\". Make sure to keep the quotes!

You will also need to go into main.py, down to the last line, and replace password=config.PASSWORD with access_token=config.ACCESS_TOKEN.

What is sso_token?

SSO token is a Single Sign On token, employed by the likes of Mozilla, and is often used for SAML. Chances are, if you don't know what it is, you definitely don't need it. And if you do need it, you already know what it is, why you need it, and how to get it.

"},{"location":"guides/001-getting-started/#actually-running-the-bot","title":"Actually running the bot","text":"

This is the really simple part, actually. All you need to do now is run main.py!

(venv) [me@host test-niobot]$ python main.py\n<insert log spam here>\nBot is ready!\n<insert log spam here>\n

Its taking FOREVER to log in! is something going wrong?

Nope. It can often take a while (upwards of five minutes in some cases!) for the bot to log in. This is because, when you first start the bot, it has to sync your entire state with the server. This often results in a LOT of IO, and a lot of network waiting, etc.

You can speed up this process in the future by:

  • Making sure you have store_path and a valid store in your configuration. Stores mean that the bot doesn't have to re-sync everything every time it starts up.
  • Using an access token instead of a password. This means that the bot doesn't have to log in, and can just start syncing immediately, even from the last time it was stopped, which saves a very very large portion of the time taken
"},{"location":"guides/001-getting-started/#interesting-log-output","title":"Interesting log output","text":"

You may notice that, if you enabled logging, you get some interesting log output.

Some things you will want to keep an eye out for:

  • INFO:niobot.client:Encryption support enabled automatically. - This means that you have set up requirements for the bot to use encryption, and it has detected that it can use encryption, and automatically enabled it, which is good!
  • DEBUG:niobot.client:<module '...' from '...'> does not have its own setup() - auto-discovering commands and events - This means that the bot has detected a module, and is automatically loading it. This is good for most cases. You should only worry about this message if you defined your own setup function.
  • DEBUG:niobot.client:Registered command <Command name='...' aliases=[...] disabled=...> into <command_name> - This simply means a command has been added to the internal register.
  • DEBUG:niobot.client:Added event listener <function <function_name> at <address>> for '<event_name>' - Like the above, this simply means an event has been added to the internal register.
"},{"location":"guides/001-getting-started/#and-thats-it","title":"And that's it!","text":"

You've successfully made a bot, and got it running!

"},{"location":"guides/001-getting-started/#wait-how-do-i-use-it","title":"Wait, how do I use it?","text":"

nio-bot has a handy dandy auto-join feature - if you just invite your bot's user to a room, assuming all is correct, within a couple seconds, your bot will automatically join your room!

Then, you can run !help to get a list of commands, and !help <command> to get help on a specific command.

"},{"location":"guides/001-getting-started/#final-product","title":"Final product","text":"config.py
HOMESERVER = \"https://matrix.org\"  # or your homeserver\nUSER_ID = \"@my-bot:matrix.org\"  # your bot account's user ID\nACCESS_TOKEN = \"syt_<...>\"  # your bot account's access token\n
main.py
import niobot\nimport logging\nimport config\n\nlogging.basicConfig(level=logging.INFO, filename=\"bot.log\")\n\nbot = niobot.NioBot(\n    homeserver=config.HOMESERVER,\n    user_id=config.USER_ID,\n    device_id='my-device-id',\n    store_path='./store',\n    command_prefix=\"!\",\n    owner_id=\"@my-matrix-username:matrix.org\"\n)\n# We also want to load `fun.py`'s commands before starting:\nbot.mount_module(\"fun\")\n\n@bot.on_event(\"ready\")\nasync def on_ready(_):\n    # That first argument is needed as the first result of the sync loop is passed to ready. Without it, this event\n    # will fail to fire, and will cause a potentially catasrophic failure.\n    print(\"Bot is ready!\")\n\n\n@bot.command()\nasync def ping(ctx):  # can be invoked with \"!ping\"\n    await ctx.respond(\"Pong!\")\n\nbot.run(access_token=config.ACCESS_TOKEN)\n
fun.py
import niobot\n\n\nclass MyFunModule(niobot.Module):  # subclassing niobot.Module is mandatory for auto-detection.\n    def __init__(self, bot):\n        self.bot = bot  # bot is the NioBot instance you made in main.py!\n\n    @niobot.command()\n    async def hello(self, ctx):\n        await ctx.respond(\"Hello %s!\" % ctx.event.sender)\n
"},{"location":"guides/001-getting-started/#why-is-logging-in-with-a-password-so-bad","title":"Why is logging in with a password so bad?","text":"

Logging in with a password carries more risk than logging in with a token, and is more prone to error. When you log in with a password with nio-bot, you need to store that password. If your password is leaked, an attacker can create as many sessions as they want, reset your password, etc. However, with an access token, they're limited to that session, and cannot reset your password.

Furthermore, if you log in with a password, some servers may generate you a completely new session, which is very slow, and can easily break any e2ee you have. Unless you are very careful with your environment, you may find yourself with slow startup times, session spam, and decryption errors.

What you should do instead is get an access token.

If you already know how to get yours, that's great! Otherwise, niocli has the solution:

$ niocli get-access-token\n

This will log into the account when prompted, and will grab you an access token, spitting it out into your terminal.

From there, you can replace bot.run(password=\"...\") with bot.run(access_token=\"...\"), and you're good to go!

"},{"location":"guides/002-a-simple-bot/","title":"A simple bot - example","text":"

Let's take the following code and break it down, so that you can better understand how niobot works:

import niobot\n\n\nbot = niobot.NioBot(\n    homeserver=\"https://matrix.org\",\n    user_id=\"@my_username:matrix.org\",\n    device_id=\"my_device_name\",  # defaults to the easily recognisable 'nio-bot'.\n    store_path=\"./store\",  # See the 'What is the store?' section for more info.\n    command_prefix=\"!\",\n    case_insensitive=True,  # means `!PING` and `!piNG` will both work and run `!ping`.\n    owner_id=\"@owner:matrix.org\",  # The user ID who owns this bot. Optional, but required for bot.is_owner(...).#\n    # Here, you can pass extra options that you would usually pass to `nio.AsyncClient`. Lets pass a proxy:\n    proxy=\"socks5://username:password@host:port\"\n)\n\n\n# Now, we want to register a command. NioBot uses the decorator `@NioBot.command()` for this.\n# This decorator takes a few arguments, but the only one you'll use most of the time is `name`.\n# There are also other arguments though.\n# For now we'll register a simple `ping` command.\n@bot.command(name=\"ping\")\n# We now need to define the function\nasync def ping(ctx: niobot.Context):\n    \"\"\"Shows the latency between events\"\"\"\n    # So a few things have happened here:\n    # `async def` makes this command asynchronous. This means that it can `await` other things.\n    # `ctx` is the only argument this command takes. By default, all niobot commands must take at least one argument,\n    # which is the command's context.\n    # We also then typehinted it with `niobot.Context`. This isn't critical to make the bot run, however if you're using\n    # an IDE like PyCharm, or just a workspace editor with intellisense like Visual Studio Code, it will help you\n    # to see what attributes and functions `niobot.Context` has without needing to check the documentation.\n    # Anyway, lets write the command itself.\n    # First, we need to measure the latency. NioBot has a handy function for this:\n    latency_ms = bot.latency(ctx.message)\n    # `bot.latency` measures the latency between when the event was dispatched by the server, and when the bot\n    # received it. It then returns the latency in milliseconds.\n    # `Context.message` is the event that triggered this command.\n    # Now, we need to reply to the user.\n    await ctx.respond(\"Pong! Latency: {:.2f}ms\".format(latency_ms))\n    # And that's it! We've written our first command.\n    # `Context.respond` always sends a reply to the user who sent the command.\n    # To send a message without a reply, you can use `NioBot.send_message`.\n\n\n# And while we're at it, we can add an event listener.\n# For this example, we'll add an event listener that tells the user if there's a command error.\n@bot.on_event(\"command_error\")\nasync def on_command_error(ctx: niobot.Context, error: Exception):\n    \"\"\"Called when a command raises an exception\"\"\"\n    # Take a look at the event reference for more information about events.\n    # Now, we can send a message to the user.\n    await ctx.respond(\"Error: {}\".format(error))\n\n\n# And while we're at it, we'll log when a user runs a command.\n@bot.on_event(\"command\")\nasync def on_command(ctx):\n    print(\"User {} ran command {}\".format(ctx.message.sender, ctx.command.name))\n\n\n# Now, we need to start our bot.\n# This is done by calling `NioBot.run()`.\n# In this example, we'll use an access token, rather than an insecure password.\n# You can get an access token through the niobot CLI:\n# $ niocli get-access-token\n# Copy the resulting access token, and then you can put it here:\nbot.run(access_token=\"my_access_token\")\n# Bear in mind that no code will run after `bot.run`. This function will block until the bot is stopped.\n# And even when the bot is stopped, its usually with an exception, so code after `bot.run` is not guaranteed to run.\n
"},{"location":"guides/003-sending-attachments/","title":"Sending attachments","text":"

Sometimes, you want to upload attachments in your chat. Be that images, videos, or other kinds of files. NioBot supports this, and it's very easy to do.

"},{"location":"guides/003-sending-attachments/#before-you-start","title":"Before you start","text":"

In order to use the majority of the features in this guide, you will need to install ffmpeg and imagemagick. These are used for thumbnail generation, and metadata detection.

You should use your package manager to install these, as they are not python packages.

Debian/UbuntuArchFedoramacOSWindows
sudo apt install ffmpeg imagemagick\n
sudo pacman -S ffmpeg imagemagick\n
sudo dnf install ffmpeg imagemagick\n
brew install ffmpeg imagemagick\n

choco install ffmpeg\nchoco install imagemagick\n
Or, install it yourself. Make sure the binaries are in your PATH:

  • gyan.dev/ffmpeg/builds/ffmpeg-git-essentials.7z
  • imagemagick.org/script/download.php
"},{"location":"guides/003-sending-attachments/#faq","title":"FAQ","text":"Why do I need to install ffmpeg and imagemagick?

imagemagick is actually optional - if you trust ffprobe to work with all of your images (in some cases it can fail to detect newer image formats), then you can skip installing it.

However, ffmpeg is required for all but file attachments. This is because in order to get some rich data, such as dimensions and duration, we need to use ffprobe to get this data. Furthermore, in the event imagemagick is not installed, the metadata fetcher falls back to ffprobe.

Not having these installed will result in a RuntimeError being raised when you try to send an attachment when it tries to fetch metadata. This is because the metadata fetcher will not be able to find ffprobe or imagemagick in your PATH.

Why does it take a couple of seconds for <attachment>.from_file() to return?

The from_file method (see: niobot.VideoAttachment.from_file, niobot.ImageAttachment.from_file, etc.) does a lot of heavy lifting in terms of preparing a file with all the bells and whistles for an upload. This means that it has to do a lot of processing, which may take a couple of seconds to return.

"},{"location":"guides/003-sending-attachments/#sending","title":"Sending:","text":""},{"location":"guides/003-sending-attachments/#regular-files","title":"Regular files","text":"

Here, regular files can be anything that isn't a video, image, or audio file. This includes text files, PDFs, etc. You can even send binary or pre-encrypted (why?) files if you want to.

Regular files are the simplest file type in niobot, in terms of code complexity and also features. Regular files do not support:

  • Thumbnails
  • Rich data
  • Previews

All you get from thumbnails is the file name, and the file size. That's it.

Anyway, here's how you could send an example text (foo.txt) file:

from niobot import NioBot, Context, FileAttachment\n...\n\n@bot.comand(name=\"upload.txt\")\nasync def upload_txt(ctx: Context):\n    \"\"\"Sends a text file!\"\"\"\n    attachment = await FileAttachment.from_file(\"file.txt\")\n    await ctx.respond(file=attachment)\n

This results in the following:

You can then click on the file to download it!

"},{"location":"guides/003-sending-attachments/#images","title":"Images","text":"

Images are a bit more complex than regular files. They support thumbnails, rich data, and previews.

Thumbnails for images

While you may think that thumbnails for images are useless, they are actually very useful for clients. Just beware though, having a larger or equal size image for your thumbnail is very counter-productive.

A valid use case for image thumbnails is for lower-resolution, likely compressed versions of the image you're sending. Paired with a blurhash, this can provide a very good \"placeholder\" image for people on painfully slow connections.

For your convenience, unless disabled, niobot will automatically generate a \"blurhash\" for your image.

A blurhash is very good for providing a \"placeholder\" image, as it is generated by a string of around 30 characters. This means people on super slow connections can see a pretty preview of the image (without much detail), instead of having an ugly loading spinner or outright blank space in place of a loading image.

For example:

This may slow down your image upload

Generating blurhashes, especially for large images, even more especially with a weak CPU, can be very slow. While this will not block your code execution, it means you must wait for the blurhash to be generated before you can do anything with the image.

You may want to disable this behaviour. See disabling extra media features.

And here's an example:

from niobot import NioBot, Context, ImageAttachment\n...\n\n\n@bot.comand(name=\"upload.png\")\nasync def upload_png(ctx: Context):\n    \"\"\"Sends a png image!\"\"\"\n    attachment = await ImageAttachment.from_file(\"file.png\")\n    await ctx.respond(file=attachment)\n

"},{"location":"guides/003-sending-attachments/#audio","title":"Audio","text":"

Audio files are actually simpler than images, however they do not support thumbnails or rich data outside of their duration.

Beware your codec!

You should aim to have your audio files as opus, vorbis, aac, flac, or mp3 encoded files, as some clients may not be able to play other formats. Also be mindful of their containers, since some (such as mkv) won't play in some clients.

Here's an example:

from niobot import NioBot, Context, AudioAttachment\n...\n\n\n@bot.comand(name=\"upload.mp3\")\nasync def upload_mp3(ctx: Context):\n    \"\"\"Sends a mp3 audio file!\"\"\"\n    attachment = await AudioAttachment.from_file(\"file.mp3\")\n    await ctx.respond(file=attachment)\n

"},{"location":"guides/003-sending-attachments/#videos","title":"Videos","text":"

Videos are the most complex file type in niobot. They support thumbnails, rich data, and previews.

Again though, NioBot makes this easy. All you need to do is pass a video file to VideoAttachment.from_file().

The same warnings apply as images, except for the blurhash. Blurhashes are not generated for videos. However, thumbnails are generated by default, with their own blurhashes. For simplicity, the video's auto-generated thumbnail is simply the first frame of the video.

Beware of your codec(s)!

A lot of matrix clients at the moment are simple HTML5-based clients - meaning they can only play a limited set of codecs out of the box.

You should aim to keep your video codecs as h264, vp8, or vp9, as these are the most widely supported. However, some native apps may not even support vp8/vp9. Use h264/avc when in doubt.

Look at audio's warning for more information about audio codecs.

Here's an example:

from niobot import NioBot, Context, VideoAttachment\n...\n\n\n@bot.comand(name=\"upload.mp4\")\nasync def upload_mp4(ctx: Context):\n    \"\"\"Sends a mp4 video!\"\"\"\n    attachment = await VideoAttachment.from_file(\"file.mp4\")\n    await ctx.respond(file=attachment)\n

"},{"location":"guides/003-sending-attachments/#unsure-which-to-use","title":"Unsure which to use?","text":"

If you aren't sure which file type you have, you can find out the most appropriate Attachment type using niobot.attachment.which - this will return either VideoAttachment, ImageAttachment, AudioAttachment, or FileAttachment, based on the mime type of the file.

Fpr example:

import random\nimport niobot\n\n\n...\n\n\n@bot.comand(name=\"upload\")\nasync def upload_mp4(ctx: Context):\n    \"\"\"Sends a random file!\"\"\"\n    files = (\"file.txt\", \"file.mp4\", \"file.mp3\", \"file.png\")\n    file_name = random.choice(files)\n\n    attachment_type = niobot.which(file_name)\n    attachment = await attachment_type.from_file(file_name)\n    # ^ can also be written as `attachment = await niobot.which(file_name).from_file(file_name)`\n    await ctx.respond(file=attachment)\n

This will upload using the appropriate file type.

"},{"location":"guides/003-sending-attachments/#disabling-extra-media-features","title":"Disabling extra media features","text":""},{"location":"guides/003-sending-attachments/#disabling-blurhash-generation","title":"Disabling blurhash generation","text":"This will harm the user experience

Disabling blurhash generation is a terrible idea - unless you make sure your uploads are a matter of kilobytes, you will always see blank spots while at least a thumbnail is loaded. Please consider alternative options.

for niobot.VideoAttachment and niobot.ImageAttachment:

from niobot import NioBot, Context, ImageAttachment, VideoAttachment\n...\n\nasync def foo():\n    attachment = await ImageAttachment.from_file(\"file.png\", generate_blurhash=False)\n    # or for Videos\n    attachment = await VideoAttachment.from_file(\"file.mp4\", generate_blurhash=False)\n

"},{"location":"guides/003-sending-attachments/#disabling-thumbnail-generation","title":"Disabling thumbnail generation","text":"This will harm the user experience

If you intend to disable thumbnail generation, you should provide your own thumbnail, or at the very least leave blurhash generation enabled.

Otherwise, while your video loads, clients will most likely just show a completely transparent box, with a loading spinner at a stretch. This leaves a massive chunk of the UI completely blank while your video loads.

for niobot.VideoAttachment only:

from niobot import NioBot, Context, ImageAttachment, VideoAttachment\n...\n\nasync def foo():\n    attachment = await VideoAttachment.from_file(\"file.mp4\", thumbnail=False)\n

"},{"location":"guides/003-sending-attachments/#disabling-rich-data","title":"Disabling rich data","text":"

A lot of rich data fields will still require values for clients to properly render the media!

In this case, \"rich data\" refers to some \"optional\" fields in media uploads, such as height, width, duration, etc. These fields are not required for the server to accept the upload, but they are often used by clients to figure out how to properly display the media.

\"Rich data\" is gathered from the get_metadata function, which itself calls ffprobe/imagemagick as a subprocess. If, for whatever reason, this is undesirable, you can avoid it.

Disabling rich data is not 100% possible, but you can avoid it by passing minimal values where it would automatically be filled in:

"},{"location":"guides/003-sending-attachments/#images_1","title":"Images","text":"
from niobot import ImageAttachment\n\nasync def foo():\n    attachment = await ImageAttachment.from_file(\"file.png\", width=0, height=0, unsafe=True)\n
"},{"location":"guides/003-sending-attachments/#videos_1","title":"Videos","text":"

from niobot import VideoAttachment\n\nasync def foo():\n    attachment = await VideoAttachment.from_file(\"file.mp4\", width=0, height=0, duration=0)\n
You may also want to consider either manually passing a thumbnail, or disabling thumbnail auto generation, as otherwise you'll still have ffmpeg/imagemagick called.

"},{"location":"guides/003-sending-attachments/#audio_1","title":"Audio","text":"
from niobot import AudioAttachment\n\nasync def foo():\n    attachment = await AudioAttachment.from_file(\"file.mp3\", duration=0)\n
"},{"location":"guides/004-creating-custom-parsers/","title":"Creating custom parsers","text":"

New in version 1.1.0b1

This feature was added in version 1.1.0b1 - pip install niobot>=1.1.0b1

NioBot is loaded with some sane defaults for basic types. If you were to pass the following function as a command:

async def my_command(ctx: Context, arg1: int, arg2: float, arg3: str):\n    ...\n
NioBot would intelligently detect ctx as the context (and realise it's not intended to be something the user provides) and arg1 as an integer, arg2 as a float, and arg3 as a string. Then, when my_command is run, the first argument would be converted to an integer from the message, the second as a float, and so on.

However, these built-in types only go so far - they're limited to a subset of python built-in types, and a couple matrix-specific things (i.e. rooms and events and matrix.to links).

This looks worryingly complicated

Pre 1.1.0b1, parsers were far too flexible and inconsistent. The old structure only required you had a singular synchronous function that took three arguments: ctx, arg, and user_input.

This was a problem for a couple reasons:

  1. The flexibility meant that it was difficult to get a uniform experience across all parsers.
  2. This was still not very flexible for customising the parsers, and often required wrappers.

However, since 1.1.0b1, the parser structure now uses two new ABC classes, Parser and StatelessParser, to ensure that all parsers are consistent and easy to use, while still being flexible and configurable. As a result of using classes though, some parsers can still feel a little bit bulky. But that's okay!

"},{"location":"guides/004-creating-custom-parsers/#creating-a-parser","title":"Creating a parser","text":"

Creating a parser is actually really easy. All the library needs from you is a class that subclasses either of the parser ABCs (see below), and implements the __call__ dunder method!

For example:

from niobot.utils.parsers import StatelessParser\nfrom niobot import CommandParserError\n\n\nclass UserParser(StatelessParser):\n    def __call__(self, ctx: Context, arg: Argument, value: str):\n        # Do some stuff here\n        if \"@\" not in value:\n            # Always raise CommandParserError when its an invalid value - this allows for proper error handling.\n            raise CommandParserError(\"Invalid user ID. Expected @user:example.com\")\n        return value[1:]  # Remove the @ from the user ID\n

You can then use this parser in your commands like so:

import niobot\nimport typing\nfrom my_parsers import UserParser\n\n\nbot = niobot.NioBot(...)\n\n\n@bot.command()\nasync def my_command(ctx: niobot.Context, user: typing.Annotated[str, UserParser]):\n    # typing.Annotated[real_type, parser] is a special type that allows you to specify a parser for a type.\n    # In your linter, `user` will be `str`, not `UserParser`.\n    await ctx.respond(\"User ID: {!s}\".format(user))\n

"},{"location":"guides/004-creating-custom-parsers/#what-if-i-need-to-await-in-my-parser","title":"What if I need to await in my parser?","text":"

If you need to use asynchronous functions in your parser, you can simply return the coroutine in __call__, like below:

class MyParser(Parser):\n    async def internal_caller(self, ctx: Context, arg: Argument, value: str):\n        # Do some stuff here\n        await asyncio.sleep(1)  # or whatever async function you need to call\n        return value\n\n    def __call__(self, *args, **kwargs):\n        return self.internal_caller(*args, **kwargs)  # this returns a coroutine.\n
By returning the unawaited coroutine, the library will intelligently detect it needs to be awaited, and will do so.

If you want to use a parser like this in your code manually, you can always use niobot.utils.force_await, which will await a coroutine if it needs awaiting, or simply returns the input if it's not a coroutine.

from niobot.utils import force_await\ncoro = MyParser()(...)\n# If you're not sure if coro is a coroutine or not, you can use force_await\nparsed = await force_await()\n# Otherwise, simply await the result\ncoro = await MyParser()(...)\n
"},{"location":"guides/004-creating-custom-parsers/#whats-the-difference-between-parser-and-statelessparser","title":"What's the difference between Parser and StatelessParser?","text":"

Great question!

With parsers, there's often a split between complicated/customisable, and fixed parsers. For example, IntegerParser is a customisable parser - You can pass options to it while initialising it, and it will use those options to parse the input. However, on the contrary, BooleanParser is a fixed parser - it does not take any options, and will always convert the input to a boolean.

Basically, StatelessParser never needs to access self while parsing. Parser can.

"},{"location":"guides/004-creating-custom-parsers/#which-should-i-choose","title":"Which should I choose?","text":"

If you're writing a parser that needs to be customisable and takes options, then you should use Parser. Otherwise, if you don't need self, then you should use StatelessParser.

"},{"location":"guides/005-direct-messages/","title":"Direct Messages","text":"

New in version 1.1.0b2

This feature was added in version 1.1.0b2 - pip install niobot>=1.1.0b2

In Matrix, Direct Messages are a bit of a loose concept. In short, a \"direct message\" in Matrix is more of a room, except with initially only two members, and a special flag, is_direct, set to true in the invite event.

However, when you join a direct room, or flag a room as direct in a client, this information is stored in account data, on the homeserver. This means that your homeserver will keep track of rooms that you're in that're flagged as \"direct\".

Direct does not mean one-to-one!

Direct rooms are not necessarily one-to-one. They can have more than two members, and they can be group chats.

The only thing that makes a room \"direct\" is the is_direct flag in the invite event.

This means that if you want to send a message to a user, you should check if the direct room you've chosen contains only two members. If not, look for another, or create one.

"},{"location":"guides/005-direct-messages/#how-do-i-send-dms-in-niobot","title":"How do I send DMs in NioBot?","text":"

NioBot handles DMs mostly transparently.

In base matrix-nio, trying to use AsyncClient.room_send and passing a user as the room_id will result in an error. You'd probably expect it to send a message to that user, so NioBot does just that!

With a bit of magic, NioBot will automatically create a direct room with the user, and send the message there. In addition, if there's already a direct room stored in account data, NioBot will use the first one it finds.

Take this example:

import niobot\n\nbot = niobot.NioBot(...)\n\n\n@bot.command(\"dm\")\nasync def send_dm(ctx: niobot.Context):\n    \"\"\"Sends you a direct message!\"\"\"\n    await bot.send_message(ctx.message.sender, \"Hello, world!\")\n

First, NioBot checks to see if there's a direct room stored in account data. If there is, it'll use that. If not, however, it will create one, and invite the user.

And that's it! Its really that simple!

"},{"location":"guides/005-direct-messages/#getting-and-creating-direct-rooms","title":"Getting and creating direct rooms","text":"

If you want to use direct rooms outside of sending messages, you can use niobot.NioBot.get_dm_rooms, and niobot.NioBot.create_dm_room.

For example:

import niobot\n\nbot = niobot.NioBot(...)\n\n\n@bot.command(\"get-dm-rooms\")\nasync def get_dm_room(ctx: niobot.Context):\n    \"\"\"Gets the direct room with the user.\"\"\"\n    rooms = await bot.get_dm_rooms(ctx.message.sender)\n\n    if not rooms:\n        await ctx.respond(\"You don't have any direct rooms!\")\n        return\n\n    rooms_text = \"\\n\".join([f\"* https://matrix.to/#/{room_id}\" for room_id in rooms])\n    await ctx.respond(f\"Your {len(rooms):,} direct rooms:\\n\\n{rooms_text}\")\n\n\n@bot.command(\"create-dm-room\")\nasync def create_dm_room(ctx: niobot.Context):\n    \"\"\"Creates a direct room with the user.\"\"\"\n    response = await bot.create_dm_room(ctx.message.sender)\n    await ctx.respond(f\"Created direct room: https://matrix.to/#/{response.room_id}\")\n

In this example, get-dm-rooms would return a count, alongside a list, of every DM room the client shares with the user. create-dm-room would create a new direct room with the user, and return the room link.

The user would've already been automatically invited to the room when it was created, so there's no need to send an invitation separately.

"},{"location":"meta/code-of-conduct/","title":"Code of Conduct - niobot chat rooms","text":"

While we try to be nice and relaxed in our chat rooms, there are still a few guidelines that you must follow when engaging with our community.

For clarification, \"chat room\" here refers to any method of communication surrounding nio-bot. This includes, but is not limited to, the Matrix rooms, and GitHub issues. Read further to see more specific moderation information.

"},{"location":"meta/code-of-conduct/#ye-old-rule-book","title":"Ye old rule book","text":"

To keep things short and snappy, not every single punishable offense is listed here. Use a bit of common sense and think, \"would I want this in my community?\". If not, neither do we.

"},{"location":"meta/code-of-conduct/#1-serve-a-warm-and-kind-environment","title":"1. Serve a warm and kind environment","text":"

Conversations in the niobot chat rooms should strive to be a warm and welcoming place for anyone and everyone. This means:

  • All conversations should be civil and respectful.
  • Drama should be taken to direct messages, unless it directly relates to niobot.
  • Hurtful language should never be used, under any circumstances, ever. This includes language that may be hateful, bigoted, or hurtful.
    • Swearing is permitted, under the authority that it is not excessive or directed.
  • Messages should have clear intent - tone is difficult to convey over text.
  • Respect the people around you.
    • If someone asks you to stop doing something, you should do so. If you have an issue with their request, take it up with them directly in direct messages, or elsewhere.
"},{"location":"meta/code-of-conduct/#2-do-not-be-disruptive","title":"2. Do not be disruptive","text":"

Causing disruption can happen in many forms, but regardless, is entirely unwelcome. Being disruptive is usually in the form of sending spam, such as large blocks of text, or several images, but could also be injecting an off-topic conversation.

"},{"location":"meta/code-of-conduct/#3-act-without-malice","title":"3. Act without malice","text":"

Any form of malice is not welcome in the niobot chat rooms. Doing ANYTHING with malice is completely unacceptable and will be treated as such. Even if you do not intend malice, if malice is perceived by a group, and you cannot appropriately explain why it was not such, then it will be treated so.

"},{"location":"meta/code-of-conduct/#4-the-standard-dont-be-a-thorn","title":"4. The standard \"don't be a thorn\"","text":"

For this, you may want to take a gander at the Matrix.org code of conduct. Their CoC has a great list of things that you should not do here, such as doxxing, harassment, or posting anything NSFW.

Their code of conduct conduct also applies to our chat rooms as it is a great CoC, aside from:

  • Contact: see contact
  • Application: Their CoC applies to matrix.org, and our rooms.
  • Affiliation: We are not affiliated with matrix.org, we are simply using their CoC in good faith.
"},{"location":"meta/code-of-conduct/#moderation","title":"Moderation","text":"

Moderation in niobot matrix rooms is done by the Draupnir (@draupnir:nexy7574.co.uk) bot. This bot has the power level to mute, kick, ban, and serverban (ACL) in all niobot rooms.

Our instance of Draupnir is subscribed to the following moderation lists:

  • Matrix.org's ToS banlist
  • Matrix.org's CoC banlist
  • envs.net's banlist
  • The Community moderation effort (CME) banlist

Meaning, if your homeserver, or your account, is on any of those ban lists, you will not be able to communicate with our Matrix rooms. Futhermore, we also maintain our own custom ban list, however this is usually only exclusive to us before it is added to the CME.

If you violate the code of conduct, depending on the severity of the infraction, you will receive:

  1. A personal warning
  2. A temporary mute
  3. A kick
  4. A ban

Obviously, doing something like posting NSFW images will get you banned straight away, but simply getting into an argument won't.

For other platforms, moderation will be done manually, and as such more harshly.

"},{"location":"meta/code-of-conduct/#contact","title":"Contact","text":"

You can contact anyone in the niobot rooms that you see are a moderator (minus bots), however @nex ((me) in most places) is usually the administrator. You can contact me outside of matrix by looking at the \"contact\" section of my website. If that is not working, you can send an email to admin@ (domain can be nexy7574.co.uk or i-am.nexus), but I cannot guarantee a timely response.

"},{"location":"reference/attachment/","title":"Attachments","text":"

Matrix file attachments. Full e2ee support is implemented.

"},{"location":"reference/attachment/#niobot.attachment.AttachmentType","title":"AttachmentType","text":"

Bases: Enum

Enumeration containing the different types of media.

Attributes:

Name Type Description FILE 'AttachmentType'

A generic file.

AUDIO 'AttachmentType'

An audio file.

VIDEO 'AttachmentType'

A video file.

IMAGE 'AttachmentType'

An image file.

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment","title":"BaseAttachment","text":"

Bases: ABC

Base class for attachments

Note

If you pass a custom file_name, this is only actually used if you pass a io.BytesIO to file. If you pass a pathlib.Path or a string, the file name will be resolved from the path, overriding the file_name parameter.

Parameters:

Name Type Description Default file Union[str, BytesIO, PathLike, Path]

The file path or BytesIO object to upload.

required file_name Optional[str]

The name of the file. Must be specified if uploading a BytesIO object.

None mime_type Optional[str]

The mime type of the file. If not specified, it will be detected.

None size_bytes Optional[int]

The size of the file in bytes. If not specified, it will be detected.

None attachment_type AttachmentType

The type of attachment. Defaults to AttachmentType.FILE.

FILE

Attributes:

Name Type Description file Union[Path, BytesIO]

The file path or BytesIO object to upload. Resolved to a pathlib.Path object if a string is passed to __init__.

file_name str

The name of the file. If file was a string or Path, this will be the name of the file.

mime_type str

The mime type of the file.

size int

The size of the file in bytes.

type AttachmentType

The type of attachment.

url Optional[str]

The URL of the uploaded file. This is set after the file is uploaded.

keys Optional[dict[str, str]]

The encryption keys for the file. This is set after the file is uploaded.

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.size_bytes","title":"size_bytes property","text":"
size_bytes: int\n

Returns the size of this attachment in bytes.

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.as_body","title":"as_body","text":"
as_body(body: Optional[str] = None) -> dict\n

Generates the body for the attachment for sending. The attachment must've been uploaded first.

Parameters:

Name Type Description Default body Optional[str]

The body to use (should be a textual description). Defaults to the file name.

None

Returns:

Type Description dict"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.from_file","title":"from_file async classmethod","text":"
from_file(\n    file: Union[str, BytesIO, Path],\n    file_name: Optional[str] = None,\n) -> \"BaseAttachment\"\n

Creates an attachment from a file.

You should use this method instead of the constructor, as it will automatically detect all other values

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file or BytesIO to attach

required file_name Optional[str]

The name of the BytesIO file, if applicable

None

Returns:

Type Description 'BaseAttachment'

Loaded attachment.

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.from_mxc","title":"from_mxc async classmethod","text":"
from_mxc(client: 'NioBot', url: str) -> 'BaseAttachment'\n

Creates an attachment from an MXC URL.

Parameters:

Name Type Description Default client 'NioBot'

The current client instance (used to download the attachment)

required url str

The MXC:// url to download

required

Returns:

Type Description 'BaseAttachment'

The downloaded and probed attachment.

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.from_http","title":"from_http async classmethod","text":"
from_http(\n    url: str, client_session: Optional[ClientSession] = None\n) -> \"BaseAttachment\"\n

Creates an attachment from an HTTP URL.

This is not necessarily just for images, video, or other media - it can be used for any HTTP resource.

Parameters:

Name Type Description Default url str

The http/s URL to download

required client_session Optional[ClientSession]

The aiohttp client session to use. If not specified, a new one will be created.

None

Returns:

Type Description 'BaseAttachment'

The downloaded and probed attachment.

Raises:

Type Description niobot.MediaDownloadException

if the download failed.

aiohttp.ClientError

if the download failed.

niobot.MediaDetectionException

if the MIME type could not be detected.

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.size_as","title":"size_as","text":"
size_as(\n    unit: Literal[\n        \"b\", \"kb\", \"kib\", \"mb\", \"mib\", \"gb\", \"gib\"\n    ]\n) -> Union[int, float]\n

Helper function to convert the size of this attachment into a different unit.

Remember: - 1 kilobyte (KB) is 1000 bytes - 1 kibibyte (KiB) is 1024 bytes

Example

>>> import niobot\n>>> attachment = niobot.FileAttachment(\"background.png\", \"image/png\")\n>>> attachment.size_bytes\n329945\n>>> attachment.size_as(\"kb\")\n329.945\n>>> attachment.size_as(\"kib\")\n322.2119140625\n>>> attachment.size_as(\"mb\")\n0.329945\n>>> attachment.size_as(\"mib\")\n0.31466007232666016\n
Note that due to the nature of floats, precision may be lost, especially the larger in units you go.

Parameters:

Name Type Description Default unit Literal['b', 'kb', 'kib', 'mb', 'mib', 'gb', 'gib']

The unit to convert into

required

Returns:

Type Description Union[int, float]

The converted size

"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.upload","title":"upload async","text":"
upload(\n    client: \"NioBot\", encrypted: bool = False\n) -> \"BaseAttachment\"\n

Uploads the file to matrix.

Parameters:

Name Type Description Default client 'NioBot'

The client to upload

required encrypted bool

Whether to encrypt the attachment or not

False

Returns:

Type Description 'BaseAttachment'

The attachment

"},{"location":"reference/attachment/#niobot.attachment.FileAttachment","title":"FileAttachment","text":"

Bases: BaseAttachment

Represents a generic file attachment.

You should use VideoAttachment for videos, AudioAttachment for audio, and ImageAttachment for images. This is for everything else.

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to upload

required file_name Optional[str]

The name of the file

None mime_type Optional[str]

The mime type of the file

None size_bytes Optional[int]

The size of the file in bytes

None"},{"location":"reference/attachment/#niobot.attachment.ImageAttachment","title":"ImageAttachment","text":"

Bases: BaseAttachment

Represents an image attachment.

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to upload

required file_name Optional[str]

The name of the file

None mime_type Optional[str]

The mime type of the file

None size_bytes Optional[int]

The size of the file in bytes

None height Optional[int]

The height of the image in pixels (e.g. 1080)

None width Optional[int]

The width of the image in pixels (e.g. 1920)

None thumbnail Optional['ImageAttachment']

A thumbnail of the image. NOT a blurhash.

None xyz_amorgan_blurhash Optional[str]

The blurhash of the image

None

Attributes:

Name Type Description info Dict

A dict of info about the image. Contains h, w, mimetype, and size keys.

thumbnail

A thumbnail of the image. NOT a blurhash.

"},{"location":"reference/attachment/#niobot.attachment.ImageAttachment.info","title":"info property","text":"
info: Dict\n

returns the info dictionary for this image.

Use ImageAttachment.as_body()[\"info\"] instead.

"},{"location":"reference/attachment/#niobot.attachment.ImageAttachment.from_file","title":"from_file async classmethod","text":"
from_file(\n    file: Union[str, BytesIO, Path],\n    file_name: Optional[str] = None,\n    height: Optional[int] = None,\n    width: Optional[int] = None,\n    thumbnail: Optional[\"ImageAttachment\"] = None,\n    generate_blurhash: bool = True,\n    *,\n    xyz_amorgan_blurhash: Optional[str] = None,\n    unsafe: bool = False\n) -> \"ImageAttachment\"\n

Generates an image attachment

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to upload

required file_name Optional[str]

The name of the file (only used if file is a BytesIO)

None height Optional[int]

The height, in pixels, of this image

None width Optional[int]

The width, in pixels, of this image

None thumbnail Optional['ImageAttachment']

A thumbnail for this image

None generate_blurhash bool

Whether to generate a blurhash for this image

True xyz_amorgan_blurhash Optional[str]

The blurhash of the image, if known beforehand.

None unsafe bool

Whether to allow uploading of images with unsupported codecs. May break metadata detection.

False

Returns:

Type Description 'ImageAttachment'

An image attachment

"},{"location":"reference/attachment/#niobot.attachment.ImageAttachment.thumbnailify_image","title":"thumbnailify_image staticmethod","text":"
thumbnailify_image(\n    image: Union[Image, BytesIO, str, Path],\n    size: Tuple[int, int] = (320, 240),\n    resampling: Union[\"PIL.Image.Resampling\"] = BICUBIC,\n) -> Image\n

Helper function to thumbnail an image.

This function is blocking - you should use niobot.utils.run_blocking to run it.

Parameters:

Name Type Description Default image Union[Image, BytesIO, str, Path]

The image to thumbnail

required size Tuple[int, int]

The size to thumbnail to. Defaults to 320x240, a standard thumbnail size.

(320, 240) resampling Union['PIL.Image.Resampling']

The resampling filter to use. Defaults to PIL.Image.BICUBIC, a high-quality but fast resampling method. For the highest quality, use PIL.Image.LANCZOS.

BICUBIC

Returns:

Type Description Image

The thumbnail

"},{"location":"reference/attachment/#niobot.attachment.ImageAttachment.get_blurhash","title":"get_blurhash async","text":"
get_blurhash(\n    quality: Tuple[int, int] = (4, 3),\n    file: Optional[Union[str, Path, BytesIO, Image]] = None,\n    disable_auto_crop: bool = False,\n) -> str\n

Gets the blurhash of the attachment. See: woltapp/blurhash

You should crop-down your blurhash images.

Generating blurhashes can take a long time, especially on large images. You should crop-down your images to a reasonable size before generating the blurhash.

Remember, most image quality is lost - there's very little point in generating a blurhash for a 4K image. Anything over 800x600 is definitely overkill.

You can easily resize images with niobot.ImageAttachment.thumbnailify_image:

attachment = await niobot.ImageAttachment.from_file(my_image, generate_blurhash=False)\nawait attachment.get_blurhash(file=attachment.thumbnailify_image(attachment.file))\n

This will generate a roughly 320x240 thumbnail image, and generate the blurhash from that.

New!

Unless you pass disable_auto_crop=True, this function will automatically crop the image down to a reasonable size, before generating a blurhash.

Parameters:

Name Type Description Default quality Tuple[int, int]

A tuple of the quality to generate the blurhash at. Defaults to (4, 3).

(4, 3) file Optional[Union[str, Path, BytesIO, Image]]

The file to generate the blurhash from. Defaults to the file passed in the constructor.

None disable_auto_crop bool

Whether to disable automatic cropping of the image. Defaults to False.

False

Returns:

Type Description str

The blurhash

"},{"location":"reference/attachment/#niobot.attachment.VideoAttachment","title":"VideoAttachment","text":"

Bases: BaseAttachment

Represents a video attachment.

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to upload

required file_name Optional[str]

The name of the file

None mime_type Optional[str]

The mime type of the file

None size_bytes Optional[int]

The size of the file in bytes

None height Optional[int]

The height of the video in pixels (e.g. 1080)

None width Optional[int]

The width of the video in pixels (e.g. 1920)

None duration Optional[int]

The duration of the video in seconds

None thumbnail Optional['ImageAttachment']

A thumbnail of the video. NOT a blurhash.

None"},{"location":"reference/attachment/#niobot.attachment.VideoAttachment.from_file","title":"from_file async classmethod","text":"
from_file(\n    file: Union[str, BytesIO, Path],\n    file_name: Optional[str] = None,\n    duration: Optional[int] = None,\n    height: Optional[int] = None,\n    width: Optional[int] = None,\n    thumbnail: Optional[\n        Union[ImageAttachment, Literal[False]]\n    ] = None,\n    generate_blurhash: bool = True,\n) -> \"VideoAttachment\"\n

Generates a video attachment

This function auto-generates a thumbnail!

As thumbnails greatly improve user experience, even with blurhashes enabled, this function will by default create a thumbnail of the first frame of the given video if you do not provide one yourself. This may increase your initialisation time by a couple seconds, give or take!

If this is undesirable, pass thumbnail=False to disable generating a thumbnail. This is independent of generate_blurhash.

Generated thumbnails are always WebP images, so they will always be miniature, so you shouldn't notice a significant increase in upload time, especially considering your video will likely be several megabytes.

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to upload

required file_name Optional[str]

The name of the file (only used if file is a BytesIO)

None duration Optional[int]

The duration of the video, in seconds

None height Optional[int]

The height, in pixels, of this video

None width Optional[int]

The width, in pixels, of this video

None thumbnail Optional[Union[ImageAttachment, Literal[False]]]

A thumbnail for this image

None generate_blurhash bool

Whether to generate a blurhash for this image

True

Returns:

Type Description 'VideoAttachment'

An image attachment

"},{"location":"reference/attachment/#niobot.attachment.VideoAttachment.generate_thumbnail","title":"generate_thumbnail async staticmethod","text":"
generate_thumbnail(\n    video: Union[str, Path, \"VideoAttachment\"]\n) -> ImageAttachment\n

Generates a thumbnail for a video.

Parameters:

Name Type Description Default video Union[str, Path, 'VideoAttachment']

The video to generate a thumbnail for

required

Returns:

Type Description ImageAttachment

The path to the generated thumbnail

"},{"location":"reference/attachment/#niobot.attachment.AudioAttachment","title":"AudioAttachment","text":"

Bases: BaseAttachment

Represents an audio attachment.

"},{"location":"reference/attachment/#niobot.attachment.AudioAttachment.duration","title":"duration property writable","text":"
duration: Optional[int]\n

The duration of this audio in milliseconds

"},{"location":"reference/attachment/#niobot.attachment.AudioAttachment.from_file","title":"from_file async classmethod","text":"
from_file(\n    file: Union[str, BytesIO, Path],\n    file_name: Optional[str] = None,\n    duration: Optional[int] = None,\n) -> \"AudioAttachment\"\n

Generates an audio attachment

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to upload

required file_name Optional[str]

The name of the file (only used if file is a BytesIO)

None duration Optional[int]

The duration of the audio, in seconds

None

Returns:

Type Description 'AudioAttachment'

An audio attachment

"},{"location":"reference/attachment/#niobot.attachment.detect_mime_type","title":"detect_mime_type","text":"
detect_mime_type(file: Union[str, BytesIO, Path]) -> str\n

Detect the mime type of a file.

Parameters:

Name Type Description Default file Union[str, BytesIO, Path]

The file to detect the mime type of. Can be a BytesIO.

required

Returns:

Type Description str

The mime type of the file (e.g. text/plain, image/png, application/pdf, video/webp etc.)

Raises:

Type Description RuntimeError

If the magic library is not installed.

TypeError

If the file is not a string, BytesIO, or Path object.

"},{"location":"reference/attachment/#niobot.attachment.get_metadata_ffmpeg","title":"get_metadata_ffmpeg","text":"
get_metadata_ffmpeg(\n    file: Union[str, Path]\n) -> dict[str, Any]\n

Gets metadata for a file via ffprobe.

example output (JSON)

Parameters:

Name Type Description Default file Union[str, Path]

The file to get metadata for. Must be a path-like object

required

Returns:

Type Description dict[str, Any]

A dictionary containing the metadata.

"},{"location":"reference/attachment/#niobot.attachment.get_metadata_imagemagick","title":"get_metadata_imagemagick","text":"
get_metadata_imagemagick(file: Path) -> dict[str, Any]\n

The same as get_metadata_ffmpeg but for ImageMagick.

Only returns a limited subset of the data, such as one stream, which contains the format, and size, and the format, which contains the filename, format, and size.

example output (JSON)

Parameters:

Name Type Description Default file Path

The file to get metadata for. Must be a path object

required

Returns:

Type Description dict[str, Any]

A slimmed-down dictionary containing the metadata.

"},{"location":"reference/attachment/#niobot.attachment.get_metadata","title":"get_metadata","text":"
get_metadata(\n    file: Union[str, Path], mime_type: Optional[str] = None\n) -> dict[str, Any]\n

Gets metadata for a file.

This will use imagemagick (identify) for images where available, falling back to ffmpeg (ffprobe) for everything else.

Parameters:

Name Type Description Default file Union[str, Path]

The file to get metadata for.

required mime_type Optional[str]

The mime type of the file. If not provided, it will be detected.

None

Returns:

Type Description dict[str, Any]

The metadata for the file. See niobot.get_metadata_ffmpeg and niobot.get_metadata_imagemagick for more information.

"},{"location":"reference/attachment/#niobot.attachment.first_frame","title":"first_frame","text":"
first_frame(\n    file: Union[str, Path], file_format: str = \"webp\"\n) -> bytes\n

Gets the first frame of a video file.

This function creates a file on disk

In order to extract the frame, this function creates a temporary file on disk (or memdisk depending on where your tempdir is). While this file is deleted after the function is done, it is still something to be aware of. For example, if you're (worryingly) low on space, this function may fail to extract the frame due to a lack of space. Or, someone could temporarily access and read the file before it is deleted.

This also means that this function may be slow.

Parameters:

Name Type Description Default file Union[str, Path]

The file to get the first frame of. Must be a path-like object

required file_format str

The format to save the frame as. Defaults to webp.

'webp'

Returns:

Type Description bytes

The first frame of the video in bytes.

"},{"location":"reference/attachment/#niobot.attachment.generate_blur_hash","title":"generate_blur_hash","text":"
generate_blur_hash(\n    file: Union[str, Path, BytesIO, Image], *parts: int\n) -> str\n

Creates a blurhash

This function may be resource intensive

This function may be resource intensive, especially for large images. You should run this in a thread or process pool.

You should also scale any images down in order to increase performance.

See: woltapp/blurhash

"},{"location":"reference/attachment/#niobot.attachment.which","title":"which","text":"
which(\n    file: Union[BytesIO, Path, str],\n    mime_type: Optional[str] = None,\n) -> Union[\n    Type[\"FileAttachment\"],\n    Type[\"ImageAttachment\"],\n    Type[\"AudioAttachment\"],\n    Type[\"VideoAttachment\"],\n]\n

Gets the correct attachment type for a file.

This function will provide either Image/Video/Audio attachment where possible, or FileAttachment otherwise.

For example, image/png (from my_image.png) will see image/ and will return ImageAttachment, and video/mp4 (from my_video.mp4) will see video/ and will return VideoAttachment.

If the mime type cannot be mapped to an attachment type, this function will return FileAttachment.

Usage
import niobot\nimport pathlib\n\nmy_file = pathlib.Path(\"/tmp/foo.bar\")\nattachment = await niobot.which(my_file).from_file(my_file)\n# or\nattachment_type = niobot.which(my_file)  # one of the BaseAttachment subclasses\nattachment = await attachment_type.from_file(my_file)\n

Parameters:

Name Type Description Default file Union[BytesIO, Path, str]

The file or BytesIO to investigate

required mime_type Optional[str]

The optional pre-detected mime type. If this is not provided, it will be detected.

None

Returns:

Type Description Union[Type['FileAttachment'], Type['ImageAttachment'], Type['AudioAttachment'], Type['VideoAttachment']]

The correct type for this attachment (not instantiated)

"},{"location":"reference/client/","title":"Client","text":""},{"location":"reference/client/#niobot.client.NioBot","title":"NioBot","text":"

Bases: AsyncClient

The main client for NioBot.

Forcing an initial sync is slow

(for the force_initial_sync parameter) By default, nio-bot stores what the last sync token was, and will resume from that next time it starts. This allows you to start up near instantly, and makes development easier and faster.

However, in some cases, especially if you are missing some metadata such as rooms or their members, you may need to perform an initial (sometimes referred to as \"full\") sync. An initial sync will fetch ALL the data from the server, rather than just what has changed since the last sync.

This initial sync can take several minutes, especially the larger your bot gets, and should only be used if you are missing aforementioned data that you need.

Parameters:

Name Type Description Default homeserver str

The homeserver to connect to. e.g. https://matrix-client.matrix.org

required user_id str

The user ID to log in as. e.g. @user:matrix.org

required device_id str

The device ID to log in as. e.g. nio-bot

'nio-bot' store_path Optional[str]

The path to the store file. Defaults to ./store. Must be a directory.

None command_prefix Union[str, Pattern, Iterable[str]]

The prefix to use for commands. e.g. !. Can be a string, a list of strings, or a regex pattern.

required case_insensitive bool

Whether to ignore case when checking for commands. If True, this casefold()s incoming messages for parsing.

True global_message_type Literal['m.text', 'm.notice']

The message type to default to. Defaults to m.notice

'm.notice' ignore_old_events bool

Whether to simply discard events before the bot's login.

True auto_join_rooms bool

Whether to automatically join rooms the bot is invited to.

True auto_read_messages bool

Whether to automatically update read recipts

True owner_id Optional[str]

The user ID of the bot owner. If set, only this user can run owner-only commands, etc.

None max_message_cache int

The maximum number of messages to cache. Defaults to 1000.

1000 ignore_self bool

Whether to ignore messages sent by the bot itself. Defaults to False. Useful for self-bots.

True import_keys Tuple[PathLike, Optional[str]]

A key export file and password tuple. These keys will be imported at startup.

None startup_presence Literal['online', 'unavailable', 'offline', False, None]

The presence to set on startup. False disables presence altogether, and None is automatic based on the startup progress.

None default_parse_mentions bool

Whether to parse mentions in send_message by default to make them intentional.

True force_initial_sync bool

Forcefully perform a full initial sync at startup.

False use_fallback_replies bool

Whether to force the usage of deprecated fallback replies. Not recommended outside of compatibility reasons.

False"},{"location":"reference/client/#niobot.client.NioBot.supported_server_versions","title":"supported_server_versions property","text":"
supported_server_versions: List[Tuple[int, int, int]]\n

Returns the supported server versions as a list of major, minor, patch tuples.

The only time patch is >0 is when the server is using a deprecated r release. All stable releases (v1) will have patch as 0.

This property returns [(1, 1, 0)] if no server info is available.

"},{"location":"reference/client/#niobot.client.NioBot.commands","title":"commands property","text":"
commands: dict[str, Command]\n

Returns the internal command register.

Warning

Modifying any values here will update the internal register too.

Note

Aliases of commands are treated as their own command instance. You will see the same command show up as a value multiple times if it has aliases.

You can check if two commands are identical by comparing them (command1instance == command2instance)

"},{"location":"reference/client/#niobot.client.NioBot.modules","title":"modules property","text":"
modules: dict[Type, Module]\n

Returns the internal module register.

Warning

Modifying any values here will update the internal register too.

"},{"location":"reference/client/#niobot.client.NioBot.server_supports","title":"server_supports","text":"
server_supports(\n    version: Union[Tuple[int, int], Tuple[int, int, int]]\n) -> bool\n

Checks that the server supports at least this matrix version.

"},{"location":"reference/client/#niobot.client.NioBot.mxc_to_http","title":"mxc_to_http async","text":"
mxc_to_http(\n    mxc: str, homeserver: Optional[str] = None\n) -> Optional[str]\n

Converts an mxc:// URI to a downloadable HTTP URL.

This function is identical the nio.AsyncClient.mxc_to_http() function, however supports matrix 1.10 and below's unauthenticated media automatically.

Parameters:

Name Type Description Default mxc str

The mxc URI

required homeserver Optional[str]

The homeserver to download this through (defaults to the bot's homeserver)

None

Returns:

Type Description Optional[str]

an MXC URL, if applicable

"},{"location":"reference/client/#niobot.client.NioBot.latency","title":"latency staticmethod","text":"
latency(\n    event: Event, *, received_at: Optional[float] = None\n) -> float\n

Returns the latency for a given event in milliseconds

Parameters:

Name Type Description Default event Event

The event to measure latency with

required received_at Optional[float]

The optional time the event was received at. If not given, uses the current time.

None

Returns:

Type Description float

The latency in milliseconds

"},{"location":"reference/client/#niobot.client.NioBot.dispatch","title":"dispatch","text":"
dispatch(event_name: Union[str, Event], *args, **kwargs)\n

Dispatches an event to listeners

"},{"location":"reference/client/#niobot.client.NioBot.is_old","title":"is_old","text":"
is_old(event: Event) -> bool\n

Checks if an event was sent before the bot started. Always returns False when ignore_old_events is False

"},{"location":"reference/client/#niobot.client.NioBot.update_read_receipts","title":"update_read_receipts async","text":"
update_read_receipts(\n    room: Union[str, MatrixRoom], event: Event\n)\n

Moves the read indicator to the given event in the room.

This is automatically done for you.

Whenever a message is received, this is automatically called for you. As such, your read receipt will always be the most recent message. You rarely need to call this function.

Parameters:

Name Type Description Default room Union[str, MatrixRoom]

The room to update the read receipt in.

required event Event

The event to move the read receipt to.

required

Returns:

Type Description

Nothing

"},{"location":"reference/client/#niobot.client.NioBot.process_message","title":"process_message async","text":"
process_message(\n    room: MatrixRoom, event: RoomMessage\n) -> None\n

Processes a message and runs the command it is trying to invoke if any.

"},{"location":"reference/client/#niobot.client.NioBot.is_owner","title":"is_owner","text":"
is_owner(user_id: str) -> bool\n

Checks whether a user is the owner of the bot.

Parameters:

Name Type Description Default user_id str

The user ID to check.

required

Returns:

Type Description bool

Whether the user is the owner.

"},{"location":"reference/client/#niobot.client.NioBot.mount_module","title":"mount_module","text":"
mount_module(import_path: str) -> Optional[list[Command]]\n

Mounts a module including all of its commands.

Must be a subclass of niobot.commands.Module, or else this function will not work.

There may not be an event loop running when this function is called.

If you are calling this function before you call bot.run(), it is entirely possible that you don't have a running asyncio event loop. If you use the event loop in Module.__init__, you will get an error, and the module will fail the mount.

You can get around this by deferring mounting your modules until the ready event is fired, at which point not only will the first full sync have completed (meaning the bot has all of its caches populated), but the event loop will be running.

Parameters:

Name Type Description Default import_path str

The import path (such as modules.file), which would be ./modules/file.py in a file tree.

required

Returns:

Type Description Optional[list[Command]]

Optional[list[Command]] - A list of commands mounted. None if the module's setup() was called.

Raises:

Type Description ImportError

The module path is incorrect of there was another error while importing

TypeError

The module was not a subclass of Module.

ValueError

There was an error registering a command (e.g. name conflict)

"},{"location":"reference/client/#niobot.client.NioBot.unmount_module","title":"unmount_module","text":"
unmount_module(module: Module) -> None\n

Does the opposite of mounting the module. This will remove any commands that have been added to the bot from the given module.

Parameters:

Name Type Description Default module Module

The module to unmount

required"},{"location":"reference/client/#niobot.client.NioBot.get_command","title":"get_command","text":"
get_command(name: str) -> Optional[Command]\n

Attempts to retrieve an internal command

Parameters:

Name Type Description Default name str

The name of the command to retrieve

required

Returns:

Type Description Optional[Command]

The command, if found. None otherwise.

"},{"location":"reference/client/#niobot.client.NioBot.add_command","title":"add_command","text":"
add_command(command: Command) -> None\n

Adds a command to the internal register

if a name or alias is already registered, this throws a ValueError. Otherwise, it returns None.

"},{"location":"reference/client/#niobot.client.NioBot.remove_command","title":"remove_command","text":"
remove_command(command: Command) -> None\n

Removes a command from the internal register.

If the command is not registered, this is a no-op.

"},{"location":"reference/client/#niobot.client.NioBot.command","title":"command","text":"
command(name: Optional[str] = None, **kwargs)\n

Registers a command with the bot.

"},{"location":"reference/client/#niobot.client.NioBot.on_event","title":"on_event","text":"
on_event(\n    event_type: Optional[Union[str, Type[Event]]] = None\n)\n

Wrapper that allows you to register an event handler.

Event handlers must be async.

if event_type is None, the function name is used as the event type.

Please note that if you pass a Event, you are responsible for capturing errors.

"},{"location":"reference/client/#niobot.client.NioBot.set_room_nickname","title":"set_room_nickname async","text":"
set_room_nickname(\n    room: Union[str, MatrixRoom],\n    new_nickname: str = None,\n    user: Optional[Union[str, MatrixUser]] = None,\n) -> RoomPutStateResponse\n

Changes the user's nickname in the given room.

Parameters:

Name Type Description Default room Union[str, MatrixRoom]

The room to change the nickname in.

required new_nickname str

The new nickname. If None, defaults to the user's display name.

None user Optional[Union[str, MatrixUser]]

The user to update. Defaults to the bot's user.

None

Returns:

Type Description RoomPutStateResponse

The response from the server.

"},{"location":"reference/client/#niobot.client.NioBot.get_cached_message","title":"get_cached_message","text":"
get_cached_message(\n    event_id: str,\n) -> Optional[Tuple[MatrixRoom, RoomMessage]]\n

Fetches a message from the cache.

This returns both the room the message was sent in, and the event itself.

If the message is not in the cache, this returns None.

"},{"location":"reference/client/#niobot.client.NioBot.fetch_message","title":"fetch_message async","text":"
fetch_message(room_id: str, event_id: str)\n

Fetches a message from the server.

"},{"location":"reference/client/#niobot.client.NioBot.wait_for_message","title":"wait_for_message async","text":"
wait_for_message(\n    room_id: Optional[str] = None,\n    sender: Optional[str] = None,\n    check: Optional[\n        Callable[[MatrixRoom, RoomMessageText], Any]\n    ] = None,\n    *,\n    timeout: Optional[float] = None,\n    msg_type: Type[RoomMessage] = RoomMessageText\n) -> Optional[Tuple[MatrixRoom, RoomMessage]]\n

Waits for a message, optionally with a filter.

If this function times out, asyncio.TimeoutError is raised.

Parameters:

Name Type Description Default room_id Optional[str]

The room ID to wait for a message in. If None, waits for any room.

None sender Optional[str]

The user ID to wait for a message from. If None, waits for any sender.

None check Optional[Callable[[MatrixRoom, RoomMessageText], Any]]

A function to check the message with. If the function returns False, the message is ignored.

None timeout Optional[float]

The maximum time to wait for a message. If None, waits indefinitely.

None msg_type Type[RoomMessage]

The type of message to wait for. Defaults to nio.RoomMessageText.

RoomMessageText

Returns:

Type Description Optional[Tuple[MatrixRoom, RoomMessage]]

The room and message that was received.

"},{"location":"reference/client/#niobot.client.NioBot.markdown_to_html","title":"markdown_to_html async staticmethod","text":"
markdown_to_html(text: str) -> str\n

Converts markdown to HTML.

Parameters:

Name Type Description Default text str

The markdown to render as HTML

required

Returns:

Type Description str

the rendered HTML

"},{"location":"reference/client/#niobot.client.NioBot.generate_mx_reply","title":"generate_mx_reply","text":"
generate_mx_reply(\n    room: MatrixRoom, event: RoomMessageText\n) -> str\n

Fallback replies have been removed by MSC2781. Do not use this anymore.

"},{"location":"reference/client/#niobot.client.NioBot.get_dm_rooms","title":"get_dm_rooms async","text":"
get_dm_rooms() -> Dict[str, List[str]]\n
get_dm_rooms(user: Union[MatrixUser, str]) -> List[str]\n
get_dm_rooms(\n    user: Optional[Union[MatrixUser, str]] = None\n) -> Union[Dict[str, List[str]], List[str]]\n

Gets DM rooms, optionally for a specific user.

If no user is given, this returns a dictionary of user IDs to lists of rooms.

Parameters:

Name Type Description Default user Optional[Union[MatrixUser, str]]

The user ID or object to get DM rooms for.

None

Returns:

Type Description Union[Dict[str, List[str]], List[str]]

A dictionary of user IDs to lists of rooms, or a list of rooms.

"},{"location":"reference/client/#niobot.client.NioBot.create_dm_room","title":"create_dm_room async","text":"
create_dm_room(\n    user: Union[MatrixUser, str]\n) -> RoomCreateResponse\n

Creates a DM room with a given user.

Parameters:

Name Type Description Default user Union[MatrixUser, str]

The user to create a DM room with.

required

Returns:

Type Description RoomCreateResponse

The response from the server.

"},{"location":"reference/client/#niobot.client.NioBot.send_message","title":"send_message async","text":"
send_message(\n    room: Union[MatrixRoom, MatrixUser, str],\n    content: Optional[str] = None,\n    file: Optional[BaseAttachment] = None,\n    reply_to: Optional[Union[RoomMessage, str]] = None,\n    message_type: Optional[str] = None,\n    *,\n    content_type: Literal[\n        \"plain\", \"markdown\", \"html\", \"html.raw\"\n    ] = \"markdown\",\n    override: Optional[dict] = None,\n    mentions: Union[Mentions, Literal[False], None] = None\n) -> RoomSendResponse\n

Sends a message. Doesn't get any more simple than this.

DMs

As of v1.1.0, you can now send messages to users (either a nio.MatrixUser or a user ID string), and a direct message room will automatically be created for you if one does not exist, using an existing one if it does.

Content Type

Separate to message_type, content_type controls what sort of parsing and formatting will be applied to the provided content. This is useful for sending messages that are not markdown, or for sending HTML. Before, all content was assumed to be markdown, and was parsed as such. However, this may cause undesirable effects if you are sending messages that are not markdown.

  • plain - No parsing or formatting is applied, and the content is sent as-is.
  • markdown - The content is parsed as markdown and rendered as HTML, with a fallback plain text body. This is the default.
  • html - The content is sent as HTML, with no fallback to plain text. If BeautifulSoup is installed, the provided content will be sanitised and pretty-printed before sending. ** html.raw - The content is sent as HTML, with no fallback to plain text, nor sanitising or formatting.

Parameters:

Name Type Description Default room Union[MatrixRoom, MatrixUser, str]

The room or to send this message to

required content Optional[str]

The content to send. Cannot be used with file.

None file Optional[BaseAttachment]

A file to send, if any. Cannot be used with content.

None reply_to Optional[Union[RoomMessage, str]]

A message to reply to.

None message_type Optional[str]

The message type to send. If none, defaults to NioBot.global_message_type, which itself is m.notice by default.

None override Optional[dict]

A dictionary containing additional properties to pass to the body. Overrides existing properties.

None content_type Literal['plain', 'markdown', 'html', 'html.raw']

The type of content to send. Defaults to \"markdown\".

'markdown' mentions Union[Mentions, Literal[False], None]

Intentional mentions to send with the message. If not provided, or False, then auto-detected.

None

Returns:

Type Description RoomSendResponse

The response from the server.

Raises:

Type Description MessageException

If the message fails to send, or if the file fails to upload.

ValueError

You specified neither file nor content.

RuntimeError

An internal error occured. A room was created, but is not in the bot room list.

"},{"location":"reference/client/#niobot.client.NioBot.edit_message","title":"edit_message async","text":"
edit_message(\n    room: Union[MatrixRoom, str],\n    message: Union[Event, str],\n    content: str,\n    *,\n    message_type: Optional[str] = None,\n    content_type: Literal[\n        \"plain\", \"markdown\", \"html\", \"html.raw\"\n    ] = \"markdown\",\n    mentions: Optional[Mentions] = None,\n    override: Optional[dict] = None\n) -> RoomSendResponse\n

Edit an existing message. You must be the sender of the message.

You also cannot edit messages that are attachments.

Parameters:

Name Type Description Default room Union[MatrixRoom, str]

The room the message is in.

required message Union[Event, str]

The message to edit.

required content str

The new content of the message.

required message_type Optional[str]

The new type of the message (i.e. m.text, m.notice. Defaults to client.global_message_type)

None override Optional[dict]

A dictionary containing additional properties to pass to the body. Overrides existing properties.

None content_type Literal['plain', 'markdown', 'html', 'html.raw']

The type of content to send. Defaults to \"markdown\".

'markdown'

Raises:

Type Description RuntimeError

If you are not the sender of the message.

TypeError

If the message is not text.

"},{"location":"reference/client/#niobot.client.NioBot.delete_message","title":"delete_message async","text":"
delete_message(\n    room: Union[MatrixRoom, str],\n    message_id: Union[RoomMessage, str],\n    reason: Optional[str] = None,\n) -> RoomRedactResponse\n

Delete an existing message. You must be the sender of the message.

Parameters:

Name Type Description Default room Union[MatrixRoom, str]

The room the message is in.

required message_id Union[RoomMessage, str]

The message to delete.

required reason Optional[str]

The reason for deleting the message.

None

Raises:

Type Description RuntimeError

If you are not the sender of the message.

MessageException

If the message fails to delete.

"},{"location":"reference/client/#niobot.client.NioBot.add_reaction","title":"add_reaction async","text":"
add_reaction(\n    room: Union[MatrixRoom, str],\n    message: Union[RoomMessage, str],\n    emoji: str,\n) -> RoomSendResponse\n

Adds an emoji \"reaction\" to a message.

Parameters:

Name Type Description Default room Union[MatrixRoom, str]

The room the message is in.

required message Union[RoomMessage, str]

The event ID or message object to react to.

required emoji str

The emoji to react with (e.g. \u274c = \u274c)

required

Returns:

Type Description RoomSendResponse

The response from the server.

Raises:

Type Description MessageException

If the message fails to react.

"},{"location":"reference/client/#niobot.client.NioBot.redact_reaction","title":"redact_reaction async","text":"
redact_reaction(\n    room: Union[MatrixRoom, str],\n    reaction: Union[RoomSendResponse, str],\n)\n

Alias for NioBot.delete_message, but more appropriately named for reactions.

"},{"location":"reference/client/#niobot.client.NioBot.start","title":"start async","text":"
start(\n    password: Optional[str] = None,\n    access_token: Optional[str] = None,\n    sso_token: Optional[str] = None,\n) -> None\n

Starts the bot, running the sync loop.

"},{"location":"reference/client/#niobot.client.NioBot.run","title":"run","text":"
run(\n    *,\n    password: Optional[str] = None,\n    access_token: Optional[str] = None,\n    sso_token: Optional[str] = None\n) -> None\n

Runs the bot, blocking the program until the event loop exists. This should be the last function to be called in your script, as once it exits, the bot will stop running.

Note: This function is literally just asyncio.run(NioBot.start(...)), so you won't have much control over the asyncio event loop. If you want more control, you should use await NioBot.start(...) instead.

Parameters:

Name Type Description Default password Optional[str]

The password to log in with.

None access_token Optional[str]

An existing login token.

None sso_token Optional[str]

An SSO token to sign in with.

None

Returns:

Type Description None"},{"location":"reference/client/#niobot.client.NioBot.get_account_data","title":"get_account_data async","text":"
get_account_data(\n    key: str, *, room_id: str = None\n) -> Union[dict, list, None]\n

Gets account data for the currently logged in account

Parameters:

Name Type Description Default key str

the key to get

required room_id str

The room ID to get account data from. If not provided, defaults to user-level.

None

Returns:

Type Description Union[dict, list, None]

The account data, or None if it doesn't exist

"},{"location":"reference/client/#niobot.client.NioBot.set_account_data","title":"set_account_data async","text":"
set_account_data(\n    key: str, data: dict, *, room_id: str = None\n) -> None\n

Sets account data for the currently logged in account

Parameters:

Name Type Description Default key str

the key to set

required data dict

the data to set

required room_id str

The room ID to set account data in. If not provided, defaults to user-level.

None"},{"location":"reference/client/#niobot.client.NioBot.join","title":"join async","text":"
join(\n    room_id: str, reason: str = None, is_dm: bool = False\n) -> Union[JoinResponse, JoinError]\n

Joins a room. room_id must be a room ID, not alias

Parameters:

Name Type Description Default room_id str

The room ID or alias to join

required reason str

The reason for joining the room, if any

None is_dm bool

Manually marks this room as a direct message.

False"},{"location":"reference/client/#niobot.client.NioBot.room_leave","title":"room_leave async","text":"
room_leave(\n    room_id: str, reason: str = None\n) -> Union[RoomLeaveError, RoomLeaveResponse]\n

Leaves a room. room_id must be an ID, not alias

"},{"location":"reference/commands/","title":"Commands","text":"

Using commands and events is the main way to interact with the bot.

"},{"location":"reference/commands/#command-argument-detection","title":"Command argument detection","text":"

One of the most powerful features of NioBot is the command argument interpretation system. When you create a niobot command, the arguments are automatically detected, and their desired type is inferred from the type hints in the function signature.

This means that foo: str will always give you a string, bar: int will try to give you an integer, or throw an error if it cannot convert the user-given argument.

As of v1.2.0, you can take advantage of the keyword-only and positional args in Python. Normally, when you specify a function like async def mycommand(ctx, x: str), niobot will see that you want an argument, x, and will do just that. It will take the user's input, and give you the value for x. However, if the user specifies multiple words for x, it will only give the first one to the function, unless the user warps the argument in \"quotes\".

import niobot\nbot = niobot.NioBot()\n\n@bot.command()\nasync def mycommand(ctx, x: str):\n    await ctx.respond(f\"Your argument was: {x}\")\n
If you ran !mycommand hello world, the bot would respond with Your argument was: hello.

With keyword-only arguments, you can make use of \"greedy\" arguments. While you could previously do this by manually constructing the niobot.Argument type, you can now do this with the * syntax in Python.

import niobot\nbot = niobot.NioBot()\n\n@bot.command()\nasync def mycommand(ctx, *, x: str):\n    await ctx.respond(f\"Your argument was: {x}\")\n
If you ran !mycommand hello world, the bot would respond with Your argument was: hello world.

And, as for positional args, if you want to fetch a set of arguments, you can do so by specifying *args. This will give you a tuple containing every whitespace-delimited argument after the command.

import niobot\nbot = niobot.NioBot()\n\n@bot.command()\nasync def mycommand(ctx, *args: str):\n    await ctx.respond(f\"Your arguments were: {args}\")\n
If you ran !mycommand hello world, the bot would respond with Your arguments were: ('hello', 'world').

Position & KW-Only args are final and strings!

If you specify a keyword or positional argument, you cannot have any arguments afterwards. Furthermore, (currently) both of these arguments are always strings. Trying to specify another type will throw an error.

"},{"location":"reference/commands/#reference","title":"Reference","text":""},{"location":"reference/commands/#niobot.commands.Argument","title":"Argument","text":"

Represents a command argument.

Example
from niobot import NioBot, command, Argument\n\nbot = NioBot(...)\n\n@bot.command(\"echo\")\ndef echo(ctx: niobot.Context, message: str):\n    await ctx.respond(message)\n\nbot.run(...)\n

Parameters:

Name Type Description Default name str

The name of the argument. Will be used to know which argument to pass to the command callback.

required arg_type _T

The type of the argument (e.g. str, int, etc. or a custom type)

required description Optional[str]

The description of the argument. Will be shown in the auto-generated help command.

None default Any

The default value of the argument

... required bool

Whether the argument is required or not. Defaults to True if default is ..., False otherwise.

... parser Callable[[Context, Argument, str], Optional[_T]]

A function that will parse the argument. Defaults to the default parser.

... greedy bool

When enabled, will attempt to match as many arguments as possible, without raising an error. If no arguments can be parsed, is merely empty, otherwise is a list of parsed arguments.

False"},{"location":"reference/commands/#niobot.commands.Argument.internal_parser","title":"internal_parser staticmethod","text":"
internal_parser(\n    _: Context, arg: Argument, value: str\n) -> Optional[_T]\n

The default parser for the argument. Will try to convert the value to the argument type.

"},{"location":"reference/commands/#niobot.commands.Command","title":"Command","text":"

Represents a command.

Example

Note

This example uses the command decorator, but you can also use the Command class directly, but you likely won't need to, unless you want to pass a custom command class.

All that the @command decorator does is create a Command instance and add it to the bot's commands, while wrapping the function its decorating.

from niobot import NioBot, command\n\nbot = NioBot(...)\n\n@bot.command(\"hello\")\ndef hello(ctx: niobot.Context):\n    await ctx.respond(\"Hello, %s!\" % ctx.message.sender)\n\nbot.run(...)\n

Parameters:

Name Type Description Default name str

The name of the command. Will be used to invoke the command.

required callback Callable

The callback to call when the command is invoked.

required aliases Optional[list[str]]

The aliases of the command. Will also be used to invoke the command.

None description Optional[str]

The description of the command. Will be shown in the auto-generated help command.

None disabled bool

Whether the command is disabled or not. If disabled, the command will be hidden on the auto-generated help command, and will not be able to be invoked.

False arguments Optional[list[Argument]]

A list of Argument instances. Will be used to parse the arguments given to the command. ctx is always the first argument, regardless of what you put here.

None usage Optional[str]

A string representing how to use this command's arguments. Will be shown in the auto-generated help. Do not include the command name or your bot's prefix here, only arguments. For example: usage=\"<message> [times]\" will show up as [p][command] <message> [times] in the help command.

None hidden bool

Whether the command is hidden or not. If hidden, the command will be always hidden on the auto-generated help.

False greedy bool

When enabled, CommandArgumentsError will not be raised if too many arguments are given to the command. This is useful for commands that take a variable amount of arguments, and retrieve them via Context.args.

False"},{"location":"reference/commands/#niobot.commands.Command.display_usage","title":"display_usage property","text":"
display_usage: str\n

Returns the usage string for this command, auto-resolved if not pre-defined

"},{"location":"reference/commands/#niobot.commands.Command.autodetect_args","title":"autodetect_args staticmethod","text":"
autodetect_args(callback) -> list[Argument]\n

Attempts to auto-detect the arguments for the command, based on the callback's signature

Parameters:

Name Type Description Default callback

The function to inspect

required

Returns:

Type Description list[Argument]

A list of arguments. self, and ctx are skipped.

"},{"location":"reference/commands/#niobot.commands.Command.__eq__","title":"__eq__","text":"
__eq__(other)\n

Checks if another command's runtime ID is the same as this one's

"},{"location":"reference/commands/#niobot.commands.Command.can_run","title":"can_run async","text":"
can_run(ctx: Context) -> bool\n

Checks if the current user passes all of the checks on the command.

If the user fails a check, CheckFailure is raised. Otherwise, True is returned.

"},{"location":"reference/commands/#niobot.commands.Command.parse_args","title":"parse_args async","text":"
parse_args(\n    ctx: Context,\n) -> Dict[Argument, Union[Any, List[Any]]]\n

Parses the arguments for the current command.

"},{"location":"reference/commands/#niobot.commands.Command.invoke","title":"invoke async","text":"
invoke(ctx: Context) -> Coroutine\n

Invokes the current command with the given context

Parameters:

Name Type Description Default ctx Context

The current context

required

Raises:

Type Description CommandArgumentsError

Too many/few arguments, or an error parsing an argument.

CheckFailure

A check failed

"},{"location":"reference/commands/#niobot.commands.Command.construct_context","title":"construct_context","text":"
construct_context(\n    client: NioBot,\n    room: MatrixRoom,\n    src_event: RoomMessageText,\n    invoking_prefix: str,\n    meta: str,\n    cls: type = Context,\n) -> Context\n

Constructs the context for the current command.

You will rarely need to do this, the library automatically gives you a Context when a command is run.

Parameters:

Name Type Description Default client NioBot

The current instance of the client.

required room MatrixRoom

The room the command was invoked in.

required src_event RoomMessageText

The source event that triggered the command. Must be nio.RoomMessageText.

required invoking_prefix str

The prefix that triggered the command.

required meta str

The invoking string (usually the command name, however may be an alias instead)

required cls type

The class to construct the context with. Defaults to Context.

Context

Returns:

Type Description Context

The constructed Context.

"},{"location":"reference/commands/#niobot.commands.Module","title":"Module","text":"

Represents a module.

A module houses a set of commands and events, and can be used to modularise your bot, and organise commands and their respective code into multiple files and classes for ease of use, development, and maintenance.

Attributes:

Name Type Description bot

The bot instance this module is mounted to.

"},{"location":"reference/commands/#niobot.commands.Module.list_events","title":"list_events","text":"
list_events() -> Generator[dict, None, None]\n

Lists all the @event listeners registered in this module.

This returns the functions themselves. You can get the event name via result.__nio_event__[\"name\"].

"},{"location":"reference/commands/#niobot.commands.Module.__setup__","title":"__setup__","text":"
__setup__()\n

Setup function called once by NioBot.mount_module(). Mounts every command discovered.

.. warning: If you override this function, you should ensure that you call super().setup() to ensure that commands are properly registered.

"},{"location":"reference/commands/#niobot.commands.Module.__teardown__","title":"__teardown__","text":"
__teardown__()\n

Teardown function called once by NioBot.unmount_module(). Removes any command that was mounted.

.. warning: If you override this function, you should ensure that you call super().teardown() to ensure that commands are properly unregistered.

"},{"location":"reference/commands/#niobot.commands.command","title":"command","text":"
command(name: Optional[str] = None, **kwargs) -> Callable\n

Allows you to register commands later on, by loading modules.

This differs from NioBot.command() in that commands are not automatically added, you need to load them with bot.mount_module

Parameters:

Name Type Description Default name Optional[str]

The name of the command. Defaults to function.name

None kwargs

Any key-words to pass to Command

{}

Returns:

Type Description Callable"},{"location":"reference/commands/#niobot.commands.check","title":"check","text":"
check(\n    function: Callable[\n        [Context], Union[bool, Coroutine[None, None, bool]]\n    ]\n) -> Callable\n

Allows you to register checks in modules.

@niobot.command()\n@niobot.check(my_check_func)\nasync def my_command(ctx: niobot.Context):\n    pass\n

Parameters:

Name Type Description Default function Callable[[Context], Union[bool, Coroutine[None, None, bool]]]

The function to register as a check

required

Returns:

Type Description Callable

The decorated function.

"},{"location":"reference/commands/#niobot.commands.event","title":"event","text":"
event(name: Optional[Union[str, Event]] = None) -> Callable\n

Allows you to register event listeners in modules.

Parameters:

Name Type Description Default name Optional[Union[str, Event]]

the name of the event (no on_ prefix)

None

Returns:

Type Description Callable"},{"location":"reference/context/","title":"Context","text":"

For each command invoked, the first argument is always a Context instance, which holds a lot of metadata, and a few utility functions to help you write commands.

A lot of the time, these are the three main attributes you'll be using:

  • Context.room (nio.MatrixRoom) - the room the command was invoked in.
  • Context.message (nio.RoomMessageText) - the message that invoked this command.
  • Context.respond - a utility class to help you respond to the command.
"},{"location":"reference/context/#command-context","title":"Command Context","text":"

Event-based context for a command callback

"},{"location":"reference/context/#niobot.context.Context.room","title":"room property","text":"
room: MatrixRoom\n

The room that the event was dispatched in

"},{"location":"reference/context/#niobot.context.Context.client","title":"client property","text":"
client: 'NioBot'\n

The current instance of the client

"},{"location":"reference/context/#niobot.context.Context.command","title":"command property","text":"
command: 'Command'\n

The current command being invoked

"},{"location":"reference/context/#niobot.context.Context.args","title":"args property","text":"
args: list[str]\n

Each argument given to this command

"},{"location":"reference/context/#niobot.context.Context.message","title":"message property","text":"
message: RoomMessageText\n

The current message

"},{"location":"reference/context/#niobot.context.Context.original_response","title":"original_response property","text":"
original_response: Optional[RoomSendResponse]\n

The result of Context.reply(), if it exists.

"},{"location":"reference/context/#niobot.context.Context.latency","title":"latency property","text":"
latency: float\n

Returns the current event's latency in milliseconds.

"},{"location":"reference/context/#niobot.context.Context.respond","title":"respond async","text":"
respond(\n    content: Optional[str] = None,\n    file: Optional[BaseAttachment] = None,\n    reply_to: Optional[Union[RoomMessageText, str]] = None,\n    message_type: Optional[str] = None,\n    *,\n    content_type: Literal[\n        \"plain\", \"markdown\", \"html\", \"html.raw\"\n    ] = \"markdown\",\n    override: Optional[dict] = None,\n    mentions: Union[\"Mentions\", Literal[False], None] = None\n) -> ContextualResponse\n

Responds to the current event.

See niobot.NioBot.send_message for more information.

"},{"location":"reference/context/#contextual-response","title":"Contextual Response","text":"

Context class for managing replies.

Usage of this function is not required, however it is a useful utility.

"},{"location":"reference/context/#niobot.context.ContextualResponse.original_event","title":"original_event async","text":"
original_event() -> Optional[RoomMessage]\n

Fetches the current event for this response

"},{"location":"reference/context/#niobot.context.ContextualResponse.reply","title":"reply async","text":"
reply(\n    content: Optional[str] = None,\n    file: Optional[BaseAttachment] = None,\n    message_type: Optional[str] = None,\n    *,\n    content_type: Literal[\n        \"plain\", \"markdown\", \"html\", \"html.raw\"\n    ] = \"markdown\",\n    override: Optional[dict] = None\n) -> \"ContextualResponse\"\n

Replies to the current response.

This does NOT reply to the original invoking message.

See niobot.NioBot.send_message for more information.

"},{"location":"reference/context/#niobot.context.ContextualResponse.edit","title":"edit async","text":"
edit(\n    content: str,\n    *,\n    message_type: Optional[str] = None,\n    content_type: Literal[\n        \"plain\", \"markdown\", \"html\", \"html.raw\"\n    ] = \"markdown\",\n    override: Optional[dict] = None\n) -> \"ContextualResponse\"\n

Edits the current response.

See niobot.NioBot.edit_message for more information.

"},{"location":"reference/context/#niobot.context.ContextualResponse.delete","title":"delete async","text":"
delete(reason: Optional[str] = None) -> None\n

Redacts the current response.

Parameters:

Name Type Description Default reason Optional[str]

An optional reason for the redaction

None

Returns:

Type Description None

None, as there will be no more response.

"},{"location":"reference/events/","title":"Event Reference","text":""},{"location":"reference/events/#a-little-note-about-event-names","title":"A little note about event names","text":"

Event names are never prefixed with on_, so make sure you're listening to events like message, not on_message!

While trying to listen for an on_ prefixed event will still work, it will throw warnings in the console, and may be deprecated in the future.

"},{"location":"reference/events/#niobot-specific-events","title":"NioBot-specific events","text":"

There are two types of events in niobot: those that are dispatched by niobot itself, and those that're dispatched by matrix-nio. In order to keep backwards compatability, as well as high flexibility and extensibility, niobot's NioBot class actually subclasses nio.AsyncClient. This means that anything you can do with matrix-nio, you can do with niobot.

However, for simplicity, niobot dispatches its own events independently of matrix-nio. These events are listed below.

You can listen to these events with niobot.NioBot.on_event.

Example
import niobot\n\nbot = niobot.NioBot(...)\n\n\n@bot.on_event(\"ready\")\nasync def on_ready(result):\n    print(\"Bot is ready!\")\n    print(\"Logged in as:\", bot.user_id)\n\n\nbot.run(...)\n
"},{"location":"reference/events/#niobot._event_stubs.event_loop_running","title":"event_loop_running async","text":"
event_loop_running() -> Optional[Any]\n

An event that is fired once the event loop is running.

You should use this event to perform any startup tasks.

This event is fired before the bot logs in, and before the first sync() is performed.

This means that if, for example, you wanted to initialise a database, or make some HTTP request in a module, You can @[nio]bot.event(\"event_loop_running\") do it here.

Initialising a database in a module
import niobot\nimport aiosqlite\n\nclass MyModule(niobot.Module):\n    def __init__(self, bot):\n        super().__init__(bot)\n        self.db = None\n\n    @niobot.event(\"event_loop_running\")\n    async def event_loop_running(self):\n        self.db = await aiosqlite.connect(\"mydb.db\")\n        await self.db.execute(...)\n        await self.db.commit\n
"},{"location":"reference/events/#niobot._event_stubs.ready","title":"ready async","text":"
ready(result: SyncResponse) -> Optional[Any]\n

An event that is fired when the bot's first sync() is completed.

This indicates that the bot successfully logged in, synchronised with the server, and is ready to receive events.

Parameters:

Name Type Description Default result SyncResponse

The response from the sync.

required"},{"location":"reference/events/#niobot._event_stubs.message","title":"message async","text":"
message(\n    room: MatrixRoom, event: RoomMessage\n) -> Optional[Any]\n

An event that is fired when the bot receives a message in a room that it is in.

This event is dispatched before commands are processed, and as such the convenient niobot.Context is unavailable.

Not every message is a text message

As of v1.2.0, the message event is dispatched for every decrypted message type, as such including videos, images, audio, and text. Prior for v1.2.0, this was only dispatched for text messages.

Please check either the type of the event (i.e. isinstance(event, niobot.RoomMessageText)) or the event.source[\"content\"][\"msgtype\"] to determine the type of the message.

Tip

If you want to be able to use the niobot.Context in your event handlers, you should use the command event instead.

Furthermore, if you want more fine-grained control over how commands are parsed and handled, you should override niobot.NioBot.process_message instead of using the message event.

Parameters:

Name Type Description Default room MatrixRoom

The room that the message was received in.

required event RoomMessage

The raw event that triggered the message.

required"},{"location":"reference/events/#niobot._event_stubs.command","title":"command async","text":"
command(ctx: Context) -> Optional[Any]\n

This event is dispatched once a command is finished being prepared, and is about to be invoked.

This event is dispatched after the message event, but before command_complete and command_error.

This event features the original context, which can be used to access the message, the command, and the arguments.

Parameters:

Name Type Description Default ctx Context

The context of the command.

required"},{"location":"reference/events/#niobot._event_stubs.command_complete","title":"command_complete async","text":"
command_complete(\n    ctx: Context, result: Any\n) -> Optional[Any]\n

This event is dispatched after a command has been invoked, and has completed successfully.

This event features the context, which can be used to access the message, the command, and the arguments.

Parameters:

Name Type Description Default ctx Context

The context of the command.

required result Any

The result of the command (the returned value of the callback)

required"},{"location":"reference/events/#niobot._event_stubs.command_error","title":"command_error async","text":"
command_error(\n    ctx: Context, error: CommandError\n) -> Optional[Any]\n

This event is dispatched after a command has been invoked, and has completed with an error.

This event features the context, which can be used to access the message, the command, and the arguments.

Getting the original error

As the error is wrapped in a niobot.CommandError, you can access the original error by accessing the CommandError.original attribute.

@bot.event(\"command_error\")\nasync def on_command_error(ctx, error):\n    original_error = error.original\n    print(\"Error:\", original_error)\n

It is encouraged that you inform the end user about an error that has occurred, as by default the error is simply logged to the console. Don't forget, you've got the whole Context instance - use it!

Parameters:

Name Type Description Default ctx Context

The context of the command.

required error CommandError

The error that was raised.

required"},{"location":"reference/events/#niobot._event_stubs.raw","title":"raw async","text":"
raw(room: MatrixRoom, event: Event) -> Optional[Any]\n

This is a special event that is handled when you directly pass a niobot.Event to on_event.

You cannot listen to this in the traditional sense of \"on_event('name')\" as it is not a named event. But, this extensibility allows you to listen directly for events not covered by the library.

Example

The below code will listen directly for the redaction event and will print out the redaction details.

See the nio events documentation for more details and a list of available events.x

import niobot\n\n@bot.on_event(niobot.RedactionEvent)  # listen for redactions\nasync def on_redaction(room, event):\n    print(f\"{event.sender} redacted {event.redacts} for {event.reason!r} in {room.display_name}\")\n
"},{"location":"reference/events/#matrix-nio-events","title":"matrix-nio events","text":"

See the matrix-nio documentation for the base-library set of events.

Remember, you'll need to use nio.Client.add_event_callback in order to listen to these!

New in v1.2.0

You can now listen to matrix-nio events with niobot.NioBot.on_event! Just pass the raw event type to the decorator, rather than a string.

"},{"location":"reference/exceptions/","title":"Exceptions","text":""},{"location":"reference/exceptions/#niobot.exceptions.NioBotException","title":"NioBotException","text":"

Bases: Exception

Base exception for NioBot.

Warning

In some rare cases, all of exception, response and original may be None.

All other exceptions raised by this library will subclass this exception, so at least all the below are always available:

Attributes:

Name Type Description message Optional[str]

A simple humanised explanation of the issue, if available.

response Optional[ErrorResponse]

The response object from the server, if available.

exception Optional[Union[ErrorResponse, BaseException]]

The exception that was raised, if available.

original Union[ErrorResponse, BaseException, None]

The original response, or exception if response was not available.

"},{"location":"reference/exceptions/#niobot.exceptions.NioBotException.bottom_of_chain","title":"bottom_of_chain","text":"
bottom_of_chain(\n    other: Optional[Union[Exception, ErrorResponse]] = None\n) -> Union[BaseException, ErrorResponse]\n

Recursively checks the original attribute of the exception until it reaches the bottom of the chain.

This function finds you the absolute first exception that was raised.

Parameters:

Name Type Description Default other Optional[Union[Exception, ErrorResponse]]

The other exception to recurse down. If None, defaults to the exception this method is called on.

None

Returns:

Type Description Union[BaseException, ErrorResponse]

The bottom of the chain exception.

"},{"location":"reference/exceptions/#niobot.exceptions.NioBotException.__str__","title":"__str__","text":"
__str__() -> str\n

Returns a human-readable version of the exception.

"},{"location":"reference/exceptions/#niobot.exceptions.NioBotException.__repr__","title":"__repr__","text":"
__repr__() -> str\n

Returns a developer-readable version of the exception.

"},{"location":"reference/exceptions/#niobot.exceptions.GenericMatrixError","title":"GenericMatrixError","text":"

Bases: NioBotException

Exception for generic matrix errors where a valid response was expected, but got an ErrorResponse instead.

"},{"location":"reference/exceptions/#niobot.exceptions.MessageException","title":"MessageException","text":"

Bases: NioBotException

Exception for message-related errors.

"},{"location":"reference/exceptions/#niobot.exceptions.LoginException","title":"LoginException","text":"

Bases: NioBotException

Exception for login-related errors.

"},{"location":"reference/exceptions/#niobot.exceptions.MediaException","title":"MediaException","text":"

Bases: MessageException

Exception for media-related errors.

"},{"location":"reference/exceptions/#niobot.exceptions.MediaUploadException","title":"MediaUploadException","text":"

Bases: MediaException

Exception for media-uploading related errors

"},{"location":"reference/exceptions/#niobot.exceptions.MediaDownloadException","title":"MediaDownloadException","text":"

Bases: MediaException

Exception for media-downloading related errors

"},{"location":"reference/exceptions/#niobot.exceptions.MediaCodecWarning","title":"MediaCodecWarning","text":"

Bases: ResourceWarning

Warning that is dispatched when a media file is not in a supported codec.

You can filter this warning by using warnings.filterwarnings(\"ignore\", category=niobot.MediaCodecWarning)

Often times, matrix clients are web-based, so they're limited to what the browser can display. This is usually:

  • h264/vp8/vp9/av1/theora video
  • aac/opus/vorbis/mp3/pcm_* audio
  • jpg/png/webp/avif/gif images
"},{"location":"reference/exceptions/#niobot.exceptions.MetadataDetectionException","title":"MetadataDetectionException","text":"

Bases: MediaException

Exception raised when metadata detection fails. Most of the time, this is an ffmpeg-related error

"},{"location":"reference/exceptions/#niobot.exceptions.CommandError","title":"CommandError","text":"

Bases: NioBotException

Exception subclass for all command invocation related errors.

"},{"location":"reference/exceptions/#niobot.exceptions.CommandNotFoundError","title":"CommandNotFoundError","text":"

Bases: CommandError

Exception raised when a command is not found.

"},{"location":"reference/exceptions/#niobot.exceptions.CommandPreparationError","title":"CommandPreparationError","text":"

Bases: CommandError

Exception subclass for errors raised while preparing a command for execution.

"},{"location":"reference/exceptions/#niobot.exceptions.CommandDisabledError","title":"CommandDisabledError","text":"

Bases: CommandPreparationError

Exception raised when a command is disabled.

"},{"location":"reference/exceptions/#niobot.exceptions.CommandArgumentsError","title":"CommandArgumentsError","text":"

Bases: CommandPreparationError

Exception subclass for command argument related errors.

"},{"location":"reference/exceptions/#niobot.exceptions.CommandParserError","title":"CommandParserError","text":"

Bases: CommandArgumentsError

Exception raised when there is an error parsing arguments.

"},{"location":"reference/exceptions/#niobot.exceptions.CheckFailure","title":"CheckFailure","text":"

Bases: CommandPreparationError

Exception raised when a generic check call fails.

You should prefer one of the subclass errors over this generic one, or a custom subclass.

CheckFailure is often raised by the built-in checker when a check returns a falsy value without raising an error.

"},{"location":"reference/exceptions/#niobot.exceptions.NotOwner","title":"NotOwner","text":"

Bases: CheckFailure

Exception raised when the command invoker is not the owner of the bot.

"},{"location":"reference/exceptions/#niobot.exceptions.InsufficientPower","title":"InsufficientPower","text":"

Bases: CheckFailure

Exception raised when the command invoker does not have enough power to run the command.

"},{"location":"reference/exceptions/#niobot.exceptions.NotADirectRoom","title":"NotADirectRoom","text":"

Bases: CheckFailure

Exception raised when the current room is not m.direct (a DM room)

"},{"location":"reference/utils/checks/","title":"Checks","text":"

There are a few built in checks that you can make use of:

"},{"location":"reference/utils/checks/#niobot.utils.checks.is_owner","title":"is_owner","text":"
is_owner(*extra_owner_ids)\n

Requires the sender owns the bot ([NioBot.owner_id][]), or is in extra_owner_ids.

Parameters:

Name Type Description Default extra_owner_ids

A set of @localpart:homeserver.tld strings to check against.

()

Returns:

Type Description

True - the check passed.

Raises:

Type Description NotOwner

The sender is not the owner of the bot and is not in the given IDs.

"},{"location":"reference/utils/checks/#niobot.utils.checks.is_dm","title":"is_dm","text":"
is_dm(allow_dual_membership: bool = False)\n

Requires that the current room is a DM with the sender.

Parameters:

Name Type Description Default allow_dual_membership bool

Whether to allow regular rooms, but only with the client and sender as members.

False

Returns:

Type Description"},{"location":"reference/utils/checks/#niobot.utils.checks.sender_has_power","title":"sender_has_power","text":"
sender_has_power(\n    level: int, room_creator_bypass: bool = False\n)\n

Requires that the sender has a certain power level in the current room before running the command.

Parameters:

Name Type Description Default level int

The minimum power level

required room_creator_bypass bool

If the room creator should bypass the check and always be allowed, regardless of level.

False

Returns:

Type Description"},{"location":"reference/utils/checks/#niobot.utils.checks.client_has_power","title":"client_has_power","text":"
client_has_power(level: int)\n

Requires that the bot has a certain power level in the current room before running the command.

Parameters:

Name Type Description Default level int

The minimum power level

required

Returns:

Type Description"},{"location":"reference/utils/checks/#niobot.utils.checks.from_homeserver","title":"from_homeserver","text":"
from_homeserver(*homeservers: str)\n

Requires that the sender is from one of the given homeservers.

Parameters:

Name Type Description Default homeservers str

The homeservers to allowlist.

()

Returns:

Type Description"},{"location":"reference/utils/federation/","title":"Federation","text":"

There isn't a lot here aside from a homeserver resolver. A lot of the federation is already handled by matrix-nio itself, so there isn't a lot of need for federation-specific utilities.

"},{"location":"reference/utils/federation/#niobot.utils.federation.resolve_homeserver","title":"resolve_homeserver async","text":"
resolve_homeserver(domain: str) -> str\n

Resolves a given homeserver part to the actual homeserver

Parameters:

Name Type Description Default domain str

The domain to crawl

required

Returns:

Type Description str

The resolved homeserver

"},{"location":"reference/utils/help_command/","title":"The help command","text":"

NioBot comes with a built-in help command, which can be used to display information about other commands.

This built-in command is simple, slick, and most importantly, helpful. It takes one optional argument, command, which changes the output to display information about a specific command.

"},{"location":"reference/utils/help_command/#the-command-list","title":"The command list","text":"

If a command name is not passed to the help command, it will instead display a list of all available commands. The information that will be displayed will be:

  • The command's name
  • Any aliases the command has
  • The command's short description (usually first 100 characters of first line of the command's callback docstring)
  • Any arguments that're required or optional (required are encased in <brackets>, optional in [brackets])

The command is only listed if:

  • The command is not disabled (i.e. disabled=True is passed, or omitted entirely)
  • The command is not hidden (i.e. hidden=True is not passed (or is ommitted entirely))
  • The user passes all of the checks for the command

The command list is sorted alphabetically by command name, and is not paginated or seperated at all. If you want a pretty help command, you should write your own - the default one is just meant to be a happy middle ground between pretty and functional. See the next section for more information on how to do this.

"},{"location":"reference/utils/help_command/#registering-your-own-help-command","title":"Registering your own help command","text":"

If you would like to register your own help command, you need to be aware of the following:

  • The help command is a command, much like any other command, and is registered as such. You should be aware of aliases, case sensitivity, command states (e.g. disabled/enabled) and visibility (hidden/shown), checks, etc.
  • A help command is almost always a user's first impression of your bot. You should make sure that it works 100% of the time, is insanely simple to use, and is very helpful. A help command that just says \"You can use command like ?info\" is not helpful at all, and will likely turn many users away.

As of v1.2.0, the help command is now a class that you can easily subclass. This is the recommended way of doing this.

The only function that you NEED to change is respond, which is the function that is called when the help command is run. The rest is, quite literally, just dectoration.

Here's an example of a custom help command:

from niobot import DefaultHelpCommand, NioBot\n\n\nclass MyHelpCommand(DefaultHelpCommand):\n    async def respond(self, ctx, command=None):\n        if command is None:\n            # No argument was given to !help\n            await ctx.respond(\"This is a custom help command!\")\n        else:\n            # a command name was given to !help\n            await ctx.respond(f\"Help for command {command} goes here!\")\n\n\nclient = NioBot(help_command=MyHelpCommand().respond)\n

Now, when someone runs !help, they will get a custom response from the MyHelpCommand class.

help_command should be a full Command instance.

While the above code gives the response function to the help_command parameter, it is not the ideal way to do this. You should pass a niobot.Command instance to the help command instead, as this gives you a more consistent experience, with fine-grained control over the command's state, aliases, etc.

For the sake of berevity, the above code is used to demonstrate the concept of a custom help command.

"},{"location":"reference/utils/help_command/#the-defaulthelpcommand-class","title":"The DefaultHelpCommand class","text":"

The default help command for NioBot.

This is a very basic help command which lists available commands, their arguments, and a short descrption, and allows for further information by specifying the command name as an argument.

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.clean_output","title":"clean_output staticmethod","text":"
clean_output(\n    text: str,\n    *,\n    escape_user_mentions: bool = True,\n    escape_room_mentions: bool = True,\n    escape_room_references: bool = False,\n    escape_all_periods: bool = False,\n    escape_all_at_signs: bool = False,\n    escape_method: Optional[Callable[[str], str]] = None\n) -> str\n

Escapes given text and sanitises it, ready for outputting to the user.

This should always be used when echoing any sort of user-provided content, as we all know there will be some annoying troll who will just go @room for no apparent reason every 30 seconds.

Do not rely on this!

This function is not guaranteed to escape all possible mentions, and should not be relied upon to do so. It is only meant to be used as a convenience function for simple commands.

Parameters:

Name Type Description Default text str

The text to sanitise

required escape_user_mentions bool

Escape all @user:homeserver.tld mentions

True escape_room_mentions bool

Escape all @room mentions

True escape_room_references bool

Escape all #room:homeserver.tld references

False escape_all_periods bool

Escape all literal . characters (can be used to escape all links)

False escape_all_at_signs bool

Escape all literal @ characters (can be used to escape all mentions)

False escape_method Optional[Callable[[str], str]]

A custom escape method to use instead of the built-in one (which just wraps characters in \\u200b)

None

Returns:

Type Description str

The cleaned text

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.format_command_name","title":"format_command_name staticmethod","text":"
format_command_name(command: Command) -> str\n

Formats the command name with its aliases if applicable

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.format_command_line","title":"format_command_line","text":"
format_command_line(prefix: str, command: Command) -> str\n

Formats a command line, including name(s) & usage.

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.get_short_description","title":"get_short_description staticmethod","text":"
get_short_description(command: Command) -> str\n

Generates a short (<100 characters) help description for a command.

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.get_long_description","title":"get_long_description staticmethod","text":"
get_long_description(command: Command) -> str\n

Gets the full help text for a command.

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.get_default_help","title":"get_default_help async","text":"
get_default_help(\n    ctx: Context, command_name: str = None\n) -> str\n

Gets the default help text

"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.respond","title":"respond async","text":"
respond(ctx: Context, command_name: str = None) -> None\n

Displays help information about available commands

"},{"location":"reference/utils/mentions/","title":"Mentions","text":"

New in v1.2.0

This module was added in v1.2.0, so you will not be able to use this if you are using an older version of nio-bot.

See the changelog for more information.

Starting in v1.2.0, nio-bot now has the added functionality of intentional mentions, which allows you to even more finely tune who is mentioned by your messages.

Previously just including @mxid:homeserver.example or @room would create mentions, but sometimes this was undesirable (for example, echoing user input (WHICH YOU SHOULD NOT DO)).

Using Mentions, you can now control exactly how you want mentions to be created.

"},{"location":"reference/utils/mentions/#niobot.utils.mentions.Mentions","title":"Mentions","text":"

Controls the mentions of a sent event.

See: https://spec.matrix.org/v1.11/client-server-api/#user-and-room-mentions

Parameters:

Name Type Description Default room bool

Whether this event mentions the entire room

False user_ids str

List of user IDs mentioned in the event

()"},{"location":"reference/utils/mentions/#niobot.utils.mentions.Mentions.room","title":"room instance-attribute","text":"
room: bool = room\n

Whether this event mentions the entire room

"},{"location":"reference/utils/mentions/#niobot.utils.mentions.Mentions.user_ids","title":"user_ids instance-attribute","text":"
user_ids: List[str] = list(user_ids)\n

List of user IDs mentioned in the event

"},{"location":"reference/utils/mentions/#niobot.utils.mentions.Mentions.as_body","title":"as_body","text":"
as_body() -> Dict[str, Dict[str, Union[bool, List[str]]]]\n

Returns the mentions object as a body dict (e.g. {m.mentions: {room: true, user_ids: []}})

"},{"location":"reference/utils/mentions/#niobot.utils.mentions.Mentions.from_body","title":"from_body classmethod","text":"
from_body(body: dict) -> Mentions\n

Creates a mentions object from a body dict

"},{"location":"reference/utils/mentions/#niobot.utils.mentions.Mentions.add_user","title":"add_user","text":"
add_user(mxid: str) -> Mentions\n

Adds a user to the mentions list

"},{"location":"reference/utils/mentions/#example","title":"Example","text":"
from niobot import NioBot, Context, Mentions\n\n\nbot = NioBot(\n    homeserver=\"https://matrix-client.matrix.org\",\n    user_id=\"@my_user:matrix.org\",\n    command_prefix=\"!\"\n)\n\n@bot.command(\"mention\")\nasync def mention_command(ctx: Context, ping_room: bool = False):\n    \"\"\"Mentions a user.\"\"\"\n    mentions = Mentions()\n    mentions.add_user(ctx.message.sender)\n    if ping_room:\n        mentions.room = True\n    # can also be constructed as `mentions = Mentions(true, ctx.message.sender)\n    content = f\"Hello {ctx.message.sender}, from @room!\"\n    await ctx.respond(\"Hello, \" + ctx.message.sender, mentions=mentions)\n    # This message will ping ctx.message.sender. If `ping_room` is `True`, it will also ping the room, otherwise,\n    # it will only render it.\n
"},{"location":"reference/utils/mentions/#how-automatic-parsing-works","title":"How automatic parsing works","text":"

As this is a feature that may be unexpected to some users, nio-bot will automatically parse mentions if:

  1. NioBot.default_parse_mentions is True (default) AND
  2. NioBot.send_message is not given an explicit mentions= argument AND
  3. The message has a content that is not empty.

In this case, niobot will scan through the message, enable the @room ping if that string is detected in the string, and will attempt to match any user mentions in the message. This is not foolproof, and the best way to ensure mentions are parsed correctly is to manually pass the mentions you want to the Mentions object.

"},{"location":"reference/utils/mentions/#disabling-mentions","title":"Disabling mentions","text":"

If you want to send a message that contains @mentions, but don't want them to actually mention anyone, you can pass mentions=niobot.Mentions() in send_message. This will still render the mentions on the client (usually), but rest assured it did not actually mention them (i.e. they will not have received a notification).

You cannot create a mention that is not also rendered

To mention someone, you must include that in your textual body too, not just in the mentions object. If you only mention someone via the Mentions object, it will not work.

"},{"location":"reference/utils/parsers/","title":"Parsers","text":"

These are a handful of built-in parsers that you can use with niobot.Argument.

How do I use these?

To use a parser, you simply pass parser=<function> when creating Argument(). For example:

from niobot import Argument, command, NioBot\nfrom niobot.utils.parsers import float_parser\n\nbot = NioBot(...)\n\n@bot.command(\n    name=\"sum\", \n    arguments=[\n        Argument(\"num1\", parser=float_parser),\n        Argument(\"num2\", parser=float_parser)\n    ]\n)\nasync def add_numbers(ctx: Context, num1: float, num2: float):\n    await ctx.respond(\"{!s} + {!s} = {!s}\".format(num1, num2, num1 + num2))\n\nbot.run(...)\n

While this is roughly equivalent to Argument(\"num1\", type=float), it can be helpful in cases like json_parser where you need to parse complex types.

Tip

You can also create your own parsers! See Creating Parsers for more information.

This utility modules contains a handful of simple off-the-shelf parser for some basic python types.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.Parser","title":"Parser","text":"

Bases: ABC

A base model class for parsers.

This ABC defines one uniform method, which is __call__, which takes a Context instance, Argument instance, and the user-provided string value.

This parser is designed to be instantiated, and then called with the above arguments. If you want to make a simple parser that does not take additional configuration, it is recommended to use StatelessParser instead.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.StatelessParser","title":"StatelessParser","text":"

Bases: Parser, ABC

A parser base that will not be instantiated, but rather called directly.

This is useful for parsers that do not take any configuration (such as the simple BooleanParser), where a simple one-off call is enough.

Traditionally, you'd call a Parser like this:

parser = Parser(my_argument=True)\nresult = parser(ctx, arg, value)\n# or, in one line\nresult = Parser(my_argument=True)(ctx, arg, value)\n

However, for some simple parsers, there's no need to instantiate them. Instead, you can call them directly. The StatelessParser ABC adds the parse classmethod, meaning you can simply do the following:

result = Parser.parse(ctx, arg, value)\n
Which is just a shortand for the above one-liner. This offers little to no performance benefit, however can make code look cleaner.

As this ABC subclasses the regular Parser, you can still use the traditional instantiation+call method.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.StatelessParser.parse","title":"parse classmethod","text":"
parse(\n    ctx: Context, arg: Argument, value: str\n) -> Optional[Any]\n

Parses the given value using this parser without needing to call __init__() first.

Parameters:

Name Type Description Default ctx Context

The context instance

required arg Argument

The argument instance

required value str

The value to parse

required

Returns:

Type Description typing.Optional[typing.Any]

The parsed value

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.BooleanParser","title":"BooleanParser","text":"

Bases: StatelessParser

Converts a given string into a boolean. Value is casefolded before being parsed.

The following resolves to true: * 1, y, yes, true, on

The following resolves to false: * 0, n, no, false, off

The following will raise a command argument error: anything else

Returns:

Type Description bool

A parsed boolean

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.FloatParser","title":"FloatParser","text":"

Bases: StatelessParser

Converts a given string into a floating point number.

Returns:

Type Description float

A parsed floating point number

Raises:

Type Description CommandParserError

if the value is not a valid number.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.IntegerParser","title":"IntegerParser","text":"

Bases: Parser

Parses an integer, or optionally a real number.

Parameters:

Name Type Description Default allow_floats bool

Whether to simply defer non-explicit-integer values to the float parser. This results in the return type being float

False base int

The base to parse the integer in. Defaults to 10 (denary). 2 is Binary, and 16 is Hexadecimal.

10

Returns:

Type Description Union[int, float]

A parsed integer or float, depending on input & allow_floats

Raises:

Type Description CommandParserError

if the value is not a valid number.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.JSONParser","title":"JSONParser","text":"

Bases: StatelessParser

Converts a given string into a JSON object.

Performance boost

If you want this to be fast, you should install orjson. It is a drop-in replacement for the standard library. While the parser will still work without it, it may be slower, especially for larger payloads.

Returns:

Type Description Union[dict, list, str, int, float, None, bool]

The parsed JSON object

Raises:

Type Description CommandParserError

if the value is not a valid JSON object.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.RoomParser","title":"RoomParser","text":"

Bases: StatelessParser

Parses a room ID, alias, or matrix.to link into a MatrixRoom object.

This parser is async

This parser is async, and should be awaited when used manually.

Returns:

Type Description nio.MatrixRoom

The parsed room instance

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.EventParser","title":"EventParser","text":"

Bases: Parser

Parses an event reference from either its ID, or matrix.to link.

Parameters:

Name Type Description Default event_type Optional[str]

The event type to expect (such as m.room.message). If None, any event type is allowed.

None

Returns:

Type Description typing.Coroutine

The actual internal (async) parser.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.MatrixDotToParser","title":"MatrixDotToParser","text":"

Bases: Parser

Converts a matrix.to link into a MatrixRoomLink namedtuple, which consists of the room, event, and any query passed to the URL.

Parameters:

Name Type Description Default domain str

The domain to check for. Defaults to matrix.to, consistent with average client behaviour.

'matrix.to' require_room bool

Whether to require the room part of this url to be present

True require_event bool

Whether to require the event part of this url to be present

False allow_user_as_room bool

Whether to allow user links as room links

True stateless bool

If true, the link will only be parsed, not resolved. This means rooms will stay as their IDs, etc.

False

Returns:

Type Description typing.Coroutine

The actual internal (async) parser.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.MXCParser","title":"MXCParser","text":"

Bases: StatelessParser

Parses an MXC URL into a MatrixMXCUrl namedtuple, which consists of the server and media ID.

Returns:

Type Description MatrixMXCUrl (namedtuple)

The parsed MXC URL

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.MatrixUserParser","title":"MatrixUserParser","text":"

Bases: StatelessParser

Parses a string into a MatrixUser instance from matrix-nio.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.boolean_parser","title":"boolean_parser","text":"
boolean_parser(*args, **kwargs)\n

Deprecated boolean parser. Please use niobot.utils.parsers.BooleanParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.float_parser","title":"float_parser","text":"
float_parser(*args, **kwargs)\n

Deprecated float parser. Please use niobot.utils.parsers.FloatParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.integer_parser","title":"integer_parser","text":"
integer_parser(allow_floats: bool = False, base: int = 10)\n

Deprecated integer parser. Please use niobot.utils.parsers.IntegerParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.json_parser","title":"json_parser","text":"
json_parser(*args, **kwargs)\n

Deprecated integer parser. Please use niobot.utils.parsers.JSONParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.room_parser","title":"room_parser","text":"
room_parser(*args, **kwargs)\n

Deprecated room parser. Please use niobot.utils.parsers.RoomParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.event_parser","title":"event_parser","text":"
event_parser(event_type: Optional[str] = None)\n

Deprecated event parser. Please use niobot.utils.parsers.EventParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.matrix_to_parser","title":"matrix_to_parser","text":"
matrix_to_parser(\n    require_room: bool = True,\n    require_event: bool = False,\n    allow_user_as_room: bool = True,\n)\n

Deprecated matrix.to parser. Please use niobot.utils.parsers.MatrixDotToParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#niobot.utils.parsers.mxc_parser","title":"mxc_parser","text":"
mxc_parser(*args, **kwargs)\n

Deprecated MXC parser. Please use niobot.utils.parsers.MXCParser instead.

Deprecated function

This function is deprecated and will be removed in 1.2.0.

"},{"location":"reference/utils/parsers/#creating-parsers","title":"Creating Parsers","text":"The old way (pre-1.1.0)

Creating your own parser is actually really easy. All the library needs from you is a function that:

  • Takes niobot.Context as its first argument
  • Takes niobot.Argument as its second argument
  • Takes a string (the user's input) as its third argument
  • Returns a sensible value
  • Or, raises CommandArgumentsError with a helpful error message.

Do all of this, and you can very easily just pass this to Argument!

For example, if you wanted to take a datetime, you could write your own parser like this:

from datetime import datetime\nfrom niobot import Argument, command, NioBot\n\n\ndef datetime_parser(ctx: Context, arg: Argument, user_input: str):\n    try:\n        return datetime.strptime(user_input, \"%Y-%m-%d %H:%M:%S\")\n    except ValueError:\n        raise CommandArgumentsError(\"Invalid datetime format. Expected YYYY-MM-DD HH:MM:SS\")\n\nbot = NioBot(...)\n\n\n@bot.command(name=\"remindme\", arguments=[Argument(\"time\", arg_type=datetime, parser=datetime_parser)])\nasync def remind_me(ctx: Context, time: datetime):\n    await ctx.respond(\"I'll remind you at {}!\".format(time.strftime(\"%c\")))\n\nbot.run(...)\n

Creating custom parsers for nio-bot is really simple. All you need to do is subclass either Parser or StatelessParser and implement the parse method.

However, if you want some detailed information, seek the guide

"},{"location":"reference/utils/string_view/","title":"String View","text":"

This is mostly an internal utility.

The ArgumentView is mostly used by the internal command parser to parse arguments. While you will be able to use this yourself, its very unlikely that you'll ever actually need it.

This is a work in progress.

The string view does a lot of complicated maths and logic to determine arguments. It's not as simple as just splitting the string on every whitespace and calling it an argument, the ArgumentView parser has to check for quotes, escape characters, and more.

Due to the complexity of the parser, it's very likely that there are still bugs in the parser. Fixes welcome!

"},{"location":"reference/utils/string_view/#niobot.utils.string_view.ArgumentView","title":"ArgumentView","text":"

A parser designed to allow for multi-word arguments and quotes

For example, the arguments 1 \"2 3\" 4 would result in three items in the internal list: 1, 2 3, and 4

This is most useful when parsing arguments from a command, as it allows for multi-word arguments.

Parameters:

Name Type Description Default string str

The string to parse

required"},{"location":"reference/utils/string_view/#niobot.utils.string_view.ArgumentView.eof","title":"eof property","text":"
eof: bool\n

Returns whether the parser has reached the end of the string

Returns:

Type Description bool

Whether the parser has reached the end of the string (cursor is greater than or equal to the length of the string)

"},{"location":"reference/utils/string_view/#niobot.utils.string_view.ArgumentView.add_arg","title":"add_arg","text":"
add_arg(argument: str) -> None\n

Adds an argument to the argument list

Parameters:

Name Type Description Default argument str

The argument to add

required

Returns:

Type Description None

none

"},{"location":"reference/utils/string_view/#niobot.utils.string_view.ArgumentView.parse_arguments","title":"parse_arguments","text":"
parse_arguments() -> ArgumentView\n

Main parsing engine.

Returns:

Type Description ArgumentView

self

"},{"location":"reference/utils/typing/","title":"Typing helper","text":"

This utility module contains one tool: the Typing class. It is internally used in the <send/edit/delete>_message functions of NioBot, but you can use it at any point to send typing events to the chat.

"},{"location":"reference/utils/typing/#usage","title":"Usage","text":"

Context manager to manage typing notifications.

Parameters:

Name Type Description Default client NioBot

The NioBot instance

required room_id str

The room id to send the typing notification to

required timeout int

The timeout in seconds

30 persistent bool

Whether to send a typing notification every timeout seconds, to keep the typing status active

True

Warning

Nesting Typing instances for one specific room is a bad idea, as when each instance is exited, it stops typing for the given room. For example, the below will not work as expected:

from niobot import NioBot, utils\n\nbot = NioBot(...)\n\n@bot.command()\nasync def ping(ctx):\n    async with utils.Typing(ctx.client, ctx.room.room_id):\n        await ctx.respond(\"Pong!\")\n\nbot.run(...)\n

This will not work because Context.respond calls NioBot.send_message, and NioBot.send_message creates its own Typing instance. Once ctx.respond returns, the internal Typing instance is destroyed, and the typing event is stopped, as is the behaviour of exiting the context manager. This means that either if on the loop, the upper-most utils.Typing instance will simply just create a new typing notification, or will not (especially if persistent was set to False). This breaks the whole persistence of typing.

If you want to use Typing to show that you're processing something:

If you want to use Typing to show a user that your bot is \"thinking\", or similar, you should make sure you exit the instance before responding. For example:

from niobot import NioBot, Typing\nimport httpx\n\nbot = NioBot(...)\n\n@bot.command()\nasync def process(ctx):\n    \"\"\"Spends a worryingly long time making a network request.\"\"\"\n    async with Typing(ctx.client, ctx.room.room_id):\n        await httpx.get(\"https://example.com\")\n    await ctx.respond(\"Done!\")\n

Be aware that this will cause a momentary blip in the xyz is typing status, but this is unavoidable, simply due to the semi-stateless nature of this context wrapper

A potential future solution would be to implement some funky internal lock mechanism and/or just prevent nested Typing instances, but this is not a priority at the moment.

"},{"location":"reference/utils/typing/#niobot.utils.Typing.__aenter__","title":"__aenter__ async","text":"
__aenter__()\n

Starts the typing notification loop, or sends a single typing notification if not persistent.

"},{"location":"reference/utils/typing/#niobot.utils.Typing.__aexit__","title":"__aexit__ async","text":"
__aexit__(exc_type, exc, tb)\n

Cancels any existing typing loop under this instance and sends a typing notification to stop typing.

"},{"location":"reference/utils/unblock/","title":"Unblock","text":"

A common problem developers encounter when working with an asyncio event loop is long blocking code. This can be caused by a number of things, but the most common is a call to a library that is not async-aware, and has many blocking operations (such as requests, or even the built-in open() + read() functions).

To alleviate this, NioBot provides an \"unblock\" utility, which is a simple async function that will run any blocking code in the event loop executor, and returns the result, without pausing the event loop. This is equivalent to loop.run_in_executor(None, func, *args, **kwargs).

A good example

from niobot import NioBot, command\nfrom niobot.utils import run_blocking\n\nbot = NioBot(...)\n\n\n@bot.command(name=\"read\")\nasync def read_file(ctx: Context, filename: str):\n    with open(filename, \"r\") as f:\n        contents = await run_blocking(f.read)\n    await ctx.respond(contents)\n\nbot.run(...)\n
This will read the contents of a file, without blocking the event loop, unlike the following code:

A bad example

    from niobot import NioBot, command\n    from niobot.utils import run_blocking\n\n    bot = NioBot(...)\n\n\n    @bot.command(name=\"read\")\n    async def read_file(ctx: Context, filename: str):\n        with open(filename, \"r\") as f:\n            contents = f.read()\n        await ctx.respond(contents)\n\n    bot.run(...)\n
This example is bad because it will prevent any other event processing until f.read() finishes, which is really bad if the file is large, or the disk is slow. For example, if you read at 1mb/s, and you have a 10 megabyte file, you will block the event loop for approximately 10 seconds, which means your program cannot do anything in those ten seconds, and as such your bot will appear to be non-functional!

"},{"location":"reference/utils/unblock/#niobot.utils.unblocking.run_blocking","title":"run_blocking async","text":"
run_blocking(\n    function: Callable[..., T], *args: Any, **kwargs: Any\n) -> T\n

Takes a blocking function and runs it in a thread, returning the result.

You should use this for any long-running functions that may take a long time to respond that are not coroutines that you can await. For example, running a subprocess.

Example
import asyncio\nimport subprocess\nfrom niobot.utils import run_blocking\n\nasync def main():\n    result = await run_blocking(subprocess.run, [\"find\", \"*.py\", \"-type\", \"f\"], capture_output=True)\n    print(result)\n\nasyncio.run(main())\n

Parameters:

Name Type Description Default function Callable[..., T]

The function to call. Make sure you do not call it, just pass it.

required args Any

The arguments to pass to the function.

() kwargs Any

The keyword arguments to pass to the function.

{}

Returns:

Type Description T

The result of the function.

"},{"location":"reference/utils/unblock/#niobot.utils.unblocking.force_await","title":"force_await async","text":"
force_await(\n    function: Union[Callable, Coroutine],\n    *args: Any,\n    **kwargs: Any\n)\n

Takes a function, and if it needs awaiting, it will be awaited. If it is a synchronous function, it runs it in the event loop, preventing it from blocking.

This is equivalent to (pseudo):

if can_await(x):\n    await x\nelse:\n    await run_blocking(x)\n

Parameters:

Name Type Description Default function Union[Callable, Coroutine]

The function to call. Make sure you do not call it, just pass it.

required args Any

The arguments to pass to the function.

() kwargs Any

The keyword arguments to pass to the function.

{}

Returns:

Type Description

The result of the function.

"}]} \ No newline at end of file