From 5af0bdf4d0bfb4183a490014a65f1813af5e3696 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:04:11 +0000 Subject: [PATCH] Deployed 66cabb2 to master with MkDocs 1.6.1 and mike 2.1.3 --- master/changelog/index.html | 3 +- master/objects.inv | Bin 3063 -> 2911 bytes master/reference/utils/parsers/index.html | 332 ---------------------- master/search/search_index.json | 2 +- master/sitemap.xml.gz | Bin 127 -> 127 bytes 5 files changed, 3 insertions(+), 334 deletions(-) diff --git a/master/changelog/index.html b/master/changelog/index.html index e270a0a..fcc4469 100644 --- a/master/changelog/index.html +++ b/master/changelog/index.html @@ -1599,6 +1599,7 @@
8LpXkf{}3E75)|A|sw}`mN=U!vyb6F3zR4|% z08&5ZdJZXN@1ms~>^+O{nu&Tip6;zeNF~lo_!DYMGTukRI|@ZPpDH3u7!;M{8HIPc zuD5{#!RL@lwE-1rP!(YSYJU)YMiew{T@%Wh5Kye~wIlVdTk&mZ<=VfmqZp(C-aE7k zALxf>;(wZP5xKu-w^f|J-PU^zjRQdm}zvq9zf;Sh+D?vVYiCDywZ%%tVqd z%RGiFll${NV7|kq6)Zq4PtBUW-HAIWgri4eJSsY5eE |nCEmHyCKsye8%Yl6p2UpMJD+(-3@01bR`uKRACU|tj zPLn(E%A+C`C4#2~Vt+ikw8+VOG+!CDwRfkM7=|G2H#&i&0yj)-5PZFL8HeAIcWnjl z%)PR&G3T9|*kZ}SFYFl~#_nwxXx1}N&m8=+Pp#&aU)g1Q 6kK|h}Y7;h;1GlmdiNOe?N7Y2;Qk~UlGbi1f4@qa)A%=vVA`{V2dMhYzJ zw{4j$@=X{yQQ2-ZDU;j9l+Ut0^=CZr?-<{blPk#XPz%#}@A4K@V=>$-nw@nr!T6NO zor!AGp?|F;ZchpLnl@qq1Z}(i>E^YpAHA$QW1V4Ky#R@AJC{G*v@uM2nki2*;UC0w zmiO0qnQ+EB!+(@|0TP>ZE`PddjXP*B!z!dR%sQ7(Y*bbJe3M$=(&T4X4R?xhmt{z8 zVZ8+Bnb~z1YoCReq@>0*%BQlcLHs !+VWtBPKSlqZsK7Z7sxkCbym` z+-GO2HLS0F^FS^Q6-IQm`c5)o-#=o?&H80{RPEJ>GfL79vXD;IDKy~|JHq1Srn`;T z=TB|6QGYa{!GvUDQ`s(>Z$D6=5+>}yN0i0897u`8(e{9Y!#YVytX!Vo-arPBB4W8z zzXz`%j9}9>dq4h2GRxTR-+OAQI4Ou5ke31G7Wq}&^4iyKBALH}nWBai&uF`UUQv!j zkY1CRzA!;2!NX7Lch_|gjdD_dfs!I3S&AP75`XEA{TUpQDE-}ck^H!y-VjNS` +N$*oV9hKqQF$0(tT6QLm8~DUN@c{Om%OaV SX&b91s}Y-7*vkmV{Dt+9mCCh!Eo70=3c-TE`YSGZrO@K0+o0>mr%Yf z0u;DHNd=uZv7-p~@39acgD&myY6an|+#EF-#kKRb0?`zkJE1I{ sI{LhaP#F>mle z_V7sf5MoXj4_Bhu&N+V;55G7lmI!++V!up;^tR$Uo^V92j%AZ4RKB-FTq+GJxrn#L zdgGIUX$v9lQcxkQV#_e&el5Y!q^8d1^laHvFLr=6Cgh2;4kX)n%lCS3e+Vk_e*4Y; I0VvYq5BFzoR{#J2 delta 2960 zcmV;B3vcw_7WWsBg?|}__wy@UmEO*ACClt)eC%8*<4JignVpIw(cO$Cp&)cF`Sn3U zLVy5CfY2=Lvb*7VfsgCQrE7$rFn#!r+`0 m0t%aaDo3 zXy5;-=E0{A=6K(JQ>&z35qvSzI*Q8neM4cfw6$sB?dZ9IhV1Ex<7>3peGa2I&g8JWpw2-(8y%|EZT7i$BY8XNP zaEJNVPXLu2v6MA@&0#|ku4nT6ks5!d@+!2q!cEb1&Qwv_kAz_2l}sZ@*Lg%Ocq(BL zDx-3Al~B5w`hOYIPq+)Gs~la0SD#P~nX*tIkX2dGhYyy~QIb|o;j6Vn9KMvQ=kR|K zNvCw6`~usC2}3WS9AB^>;T3cx{W~lhXJg_`|IUJCV>8m)zx WG<*>5nh9UxzB<3R-BVq4yYHFs?$G5<_r*qJYAZRkcvK4iXA%rDpBmIG7G0 zH0~HpV17jf3|!*r2*J)^Ke@?T+C2>&LB%WXpMPch_aRu`FXmVWtpF5>e4Z^{yLxD< zyOKD@@28AS$r^hw2u_*xqVRv07&jT|>S+-4LRGx0^o~!!i+@}Qinl$nfl`MJRZXtM z#&}jo%1(;SX%d8aC_26{>v=2~3B+9quCgmC?bh{V-YIPqrmsUs!fh6>Uh|0BJ#CA0 zi+}zHGtkCSo?x4WpWY~JMXdMbOQXsY<(K(#^#@eH+eAJ68A385J4pLgS#U>kJJ)ikV+9B^B;Q;iKK?KDG! zrkreKFzKcl7&PTX of|dX`Z??y?5194N!@$ZyBmw%e(yrnyB#~?dH{`l6ZV|(ZgM|iePHbrz9Tk zzI6DaJjVqM6J=a5GE - - -W<;%{y|p{0ksW;@lzIEWtqfY?kL}FlqY`fDr>~fi6f2f%@YkJB=Y_YmX&PZ zdE@bB%rG2T+|AR_b f$DK481&%q8AmB%yOPEBW92QWg(8lSpz31Yw9zaoKYrCeKAOwPZDlb1ua5AQX zGP)GjW3wEv2}n_81mns-6$R{fj )_;OSW$<6<%NzdT z5;cn$`pS*|lEt!8nr)kWCW3TX#xdN=+#mM=u@yF{L1}U?ZO0wt!qL4k9vK~@K4cmE zZ *O=p~Cbn2|tQYo(4^#6tbTsRc=RF6%=yR!g#g|su zUh$<#rdNDS&<*v&H<2z7dyzQ6Rev@xMyH=o0*sfS{TZVRF@GdGGOc$5#$pMZtu?w` z)D?fA0p{Gm-o9hLzzBh5{ XFRa)C|mL4 zYnI=E<^}X#L@ubtWVk0xJM(0Mu@RTmRicfy{<#vkJt<&Q=7 Wen0utL+m483ov@uM2nki2*;UC0wmKS)uOsKHVFr}V=#3ohcPdBY`f$n8kiFAfp zRr$n5mBr6Dsr8*HKf7wUQ;fSzLuw1_DLBu}uCreIEW9KoHLg)Um0b+-FOblGKn3=~PzKgwHdf -NqZ@r#9Opn$TceGO?*_SLL^#SC9!awtvDs#u) m(tu^18tG@;87a5zDIsd+-v%2sXV#;K!dxW*OT91)f?m zP6#6Y0Dg>`tD$CzHY=)t8pY$wxaGaL-9)TDgMa;di=#Y{Aaew~8hqFN^|1&F3)n34 znnw|sF#=6si_2%Yy@$BM0`grLO=bSV1f2!@`$`cqBXef>;nzs|yJV0=xkj=;2@xJF z#SdcfaL4`(6c3dCVFn)jxSyWkL5};G7#`% `gYJOqW+u&uPDj=zU3&fI$m7N zaox8V_v2iPgZ;GSbkK|8ej8yM#yL#y!KJu0%7YzzmR+BuIjf;O$-CGYYqQ6_;A2 )O?Ubsux(S6w`i!!Q*aYP~e-!RpH}Lw}an z>(+V4dEk@~&iW_V7fWoLBPSTeKMl&dwxAq|7ICA~#wQm>?@S;lbzxXMKt8!JIeNl4 zsSCs4O!CQqz$8!qFc3Xr`ZJD_j{)~f8>f2cwA6(YBM&D3S{NsFDOi1YKG`rUyT|Bt zs>5aGgR{Cmd|*~Df)C8G0Pw-trhm;oEa&{#3uvC>_@Hc?e0x~QcP2;KBljmqiNkj( z$L+s#dzI2oqjL(Y>{VVI6wB^sEaI@N0_3(*J3ZkLUmeQ~PpEkCj6W9 i* z1Jky>vr9pStcoqSfc9%~h9)&PHfPNiE%jmtSe?(FIBQ3;jko;Gk?rS?cnREZzxh9G GLL!&ZV7Kl7 diff --git a/master/reference/utils/parsers/index.html b/master/reference/utils/parsers/index.html index ca3b664..869db51 100644 --- a/master/reference/utils/parsers/index.html +++ b/master/reference/utils/parsers/index.html @@ -943,78 +943,6 @@ - - - - boolean_parser - - - -
- -- - - - float_parser - - - -
- -- - - - integer_parser - - - -
- -- - - - json_parser - - - -
- -- - - - room_parser - - - -
- -- - - - event_parser - - - -
- -- - - - matrix_to_parser - - - -
- -- - - - mxc_parser - - -
- @@ -1260,78 +1188,6 @@
Deprecated boolean parser. Please use niobot.utils.parsers.BooleanParser instead.
-Deprecated function
-This function is deprecated and will be removed in 1.2.0.
-Deprecated float parser. Please use niobot.utils.parsers.FloatParser instead.
-Deprecated function
-This function is deprecated and will be removed in 1.2.0.
-Deprecated integer parser. Please use niobot.utils.parsers.IntegerParser instead.
-Deprecated function
-This function is deprecated and will be removed in 1.2.0.
-Deprecated integer parser. Please use niobot.utils.parsers.JSONParser instead.
-Deprecated function
-This function is deprecated and will be removed in 1.2.0.
-Deprecated room parser. Please use niobot.utils.parsers.RoomParser instead.
-Deprecated function
-This function is deprecated and will be removed in 1.2.0.
-Deprecated event parser. Please use niobot.utils.parsers.EventParser instead.
-Deprecated function
-This function is deprecated and will be removed in 1.2.0.
-matrix_to_parser(
- require_room: bool = True,
- require_event: bool = False,
- allow_user_as_room: bool = True,
-)
-
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.
-Deprecated MXC parser. Please use niobot.utils.parsers.MXCParser instead.
-Deprecated function
-This function is deprecated and will be removed in 1.2.0.
-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.
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.
NioBot contains all of the features of matrix-nio, plus a few more:
[niobot.Context][]
object, given to every command.[niobot.NioBot][]
, and you're ready to go.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!
force_write
properly<instance> has no attribute 'mro'
error when initialising auto-detected argumentsniocli get-access-token
crashing on windowsNioBot
throwing a warning about failing key uploads without logging the actual errorcommand_prefix
to be an iterable, converting single-strings into a single-item iterable too.message
event to be any nio.RoomMessage, not just Text
.xyzamorganblurhash
into ImageAttachment
automatic_markdown_parser
option and functionality in NioBotniobot.utils.parsers.EventParser
raising an error when usedname
parameter from niobot checksnio.Event
types to event listenerstyping.Optional
in automatic argument detection*args
in automatic argument detectionnull
for metadata, which may cause incorrect client behavioursniobot.utils.Mentions
to handle intentional mentions in messagesniobot.util.help_command.help_command_callback
was removed, in line with deprecation./_matrix/client/versions
to fetch server version metadata.niobot.utils.help_command.DefaultHelpCommand
, to make subclassing easier.default_help_command
was replaced with DefaultHelpCommand().respond
.Command.can_run(ctx)
, which runs through and makes sure that all of the command checks pass.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)sync_full_state=False
.niobot.NioBot.join
throwing a JSON EOF in some casesreason
parameter to niobot.NioBot.join
and niobot.NioBot.room_leave
as optional stringsniobot.NioBot.send_message
will now automatically parse mentions if not explicitly provided, to take full advantage of intentional mentions.force_initial_sync
to niobot.NioBot
, which will force the bot to sync all rooms before starting the event loop.force_write
parameter in some BaseAttachment
(and subclass) methods. (2024-06-15)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)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.NioBot.get_dm_rooms
raising a 401 Unauthorised error regardless of any state.NioBot.get_dm_rooms
raising a GenericMatrixError
whenever there were no DM rooms, instead of gracefully returning an empty object.NioBot.get_dm_rooms
using outdated code from before matrix-nio==0.24.0
.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 versionsThis 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":"niobot.Context.invoking_prefix
.niobot.NioBot.is_ready
, which is an asyncio.Event
.@niobot.check
)GenericMatrixError
.GPLv3
to LGPLv3
.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.ignore_self
flag to niobot.NioBot
, allowing you to choose whether the client will ignore its own messages.typing.Annotated
in commands.niobot.Module.log
was fully removed - it was never fully functional and often tripped up users as it was unsettable.niobot.Module.client
was deprecated - you should use niobot.Module.client
instead.event_id
over room_id
for the _get_id
functionContext.invoking_prefix
niobot
with nio-bot
for pip install guide.niobot.attachments.which
function.event_parser
and room_parser
force_await
now just awaits coroutines rather than casting them to a taskThis 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":"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.niobot.ImageAttachment
being unable to detect image streams.niobot.BaseAttachment
setting incorrect file propertiesniobot.ImageAttachment
no longer explicitly fails upon encountering an unknown format, it simply emits a warning, in line with niobot.VideoAttachment
.setup.py
for really old pip versions.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
.Command(arguments=[...])
.__repr__
to most objects in the library.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:
And the following installed on the machine you want to run the bot on:
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.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:
config.py
file to store our configuration.main.py
file to store our bot code.fun.py
file to store a module (later on).And you'll need a directory:
store
- this is where nio-bot will store its database and other files.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.
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.
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
.
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}!\"
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
.
sso_token
? SSO token is a S
ingle S
ign O
n 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.
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:
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.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.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.
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!
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/UbuntuArchFedoramacOSWindowssudo 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: 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.
<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.
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:
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 imagesWhile 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 experienceDisabling 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:
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!
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.
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
.
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:
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:
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:
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:
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.
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 DescriptionFILE
'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 Defaultfile
Union[str, BytesIO, PathLike, Path]
The file path or BytesIO object to upload.
requiredfile_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 Descriptionfile
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_bytesproperty
","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 Defaultbody
Optional[str]
The body to use (should be a textual description). Defaults to the file name.
None
Returns:
Type Descriptiondict
"},{"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 Defaultfile
Union[str, BytesIO, Path]
The file or BytesIO to attach
requiredfile_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_mxcasync
classmethod
","text":"from_mxc(client: 'NioBot', url: str) -> 'BaseAttachment'\n
Creates an attachment from an MXC URL.
Parameters:
Name Type Description Defaultclient
'NioBot'
The current client instance (used to download the attachment)
requiredurl
str
The MXC:// url to download
requiredReturns:
Type Description'BaseAttachment'
The downloaded and probed attachment.
"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.from_http","title":"from_httpasync
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 Defaulturl
str
The http/s URL to download
requiredclient_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 Descriptionniobot.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 Defaultunit
Literal['b', 'kb', 'kib', 'mb', 'mib', 'gb', 'gib']
The unit to convert into
requiredReturns:
Type DescriptionUnion[int, float]
The converted size
"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.upload","title":"uploadasync
","text":"upload(\n client: \"NioBot\", encrypted: bool = False\n) -> \"BaseAttachment\"\n
Uploads the file to matrix.
Parameters:
Name Type Description Defaultclient
'NioBot'
The client to upload
requiredencrypted
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 Defaultfile
Union[str, BytesIO, Path]
The file to upload
requiredfile_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 Defaultfile
Union[str, BytesIO, Path]
The file to upload
requiredfile_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 Descriptioninfo
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":"infoproperty
","text":"info: Dict\n
returns the info dictionary for this image.
Use ImageAttachment.as_body()[\"info\"]
instead.
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 Defaultfile
Union[str, BytesIO, Path]
The file to upload
requiredfile_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_imagestaticmethod
","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 Defaultimage
Union[Image, BytesIO, str, Path]
The image to thumbnail
requiredsize
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 DescriptionImage
The thumbnail
"},{"location":"reference/attachment/#niobot.attachment.ImageAttachment.get_blurhash","title":"get_blurhashasync
","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 Defaultquality
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 Descriptionstr
The blurhash
"},{"location":"reference/attachment/#niobot.attachment.VideoAttachment","title":"VideoAttachment","text":" Bases: BaseAttachment
Represents a video attachment.
Parameters:
Name Type Description Defaultfile
Union[str, BytesIO, Path]
The file to upload
requiredfile_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 Defaultfile
Union[str, BytesIO, Path]
The file to upload
requiredfile_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_thumbnailasync
staticmethod
","text":"generate_thumbnail(\n video: Union[str, Path, \"VideoAttachment\"]\n) -> ImageAttachment\n
Generates a thumbnail for a video.
Parameters:
Name Type Description Defaultvideo
Union[str, Path, 'VideoAttachment']
The video to generate a thumbnail for
requiredReturns:
Type DescriptionImageAttachment
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":"durationproperty
writable
","text":"duration: Optional[int]\n
The duration of this audio in milliseconds
"},{"location":"reference/attachment/#niobot.attachment.AudioAttachment.from_file","title":"from_fileasync
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 Defaultfile
Union[str, BytesIO, Path]
The file to upload
requiredfile_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 Defaultfile
Union[str, BytesIO, Path]
The file to detect the mime type of. Can be a BytesIO.
requiredReturns:
Type Descriptionstr
The mime type of the file (e.g. text/plain
, image/png
, application/pdf
, video/webp
etc.)
Raises:
Type DescriptionRuntimeError
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 Defaultfile
Union[str, Path]
The file to get metadata for. Must be a path-like object
requiredReturns:
Type Descriptiondict[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 Defaultfile
Path
The file to get metadata for. Must be a path object
requiredReturns:
Type Descriptiondict[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 Defaultfile
Union[str, Path]
The file to get metadata for.
requiredmime_type
Optional[str]
The mime type of the file. If not provided, it will be detected.
None
Returns:
Type Descriptiondict[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 Defaultfile
Union[str, Path]
The file to get the first frame of. Must be a path-like object
requiredfile_format
str
The format to save the frame as. Defaults to webp.
'webp'
Returns:
Type Descriptionbytes
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
.
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 Defaultfile
Union[BytesIO, Path, str]
The file or BytesIO to investigate
requiredmime_type
Optional[str]
The optional pre-detected mime type. If this is not provided, it will be detected.
None
Returns:
Type DescriptionUnion[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 Defaulthomeserver
str
The homeserver to connect to. e.g. https://matrix-client.matrix.org
requireduser_id
str
The user ID to log in as. e.g. @user:matrix.org
requireddevice_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.
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.
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
)
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_httpasync
","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 Defaultmxc
str
The mxc URI
requiredhomeserver
Optional[str]
The homeserver to download this through (defaults to the bot's homeserver)
None
Returns:
Type DescriptionOptional[str]
an MXC URL, if applicable
"},{"location":"reference/client/#niobot.client.NioBot.latency","title":"latencystaticmethod
","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 Defaultevent
Event
The event to measure latency with
requiredreceived_at
Optional[float]
The optional time the event was received at. If not given, uses the current time.
None
Returns:
Type Descriptionfloat
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_receiptsasync
","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 Defaultroom
Union[str, MatrixRoom]
The room to update the read receipt in.
requiredevent
Event
The event to move the read receipt to.
requiredReturns:
Type DescriptionNothing
"},{"location":"reference/client/#niobot.client.NioBot.process_message","title":"process_messageasync
","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 Defaultuser_id
str
The user ID to check.
requiredReturns:
Type Descriptionbool
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 Defaultimport_path
str
The import path (such as modules.file), which would be ./modules/file.py in a file tree.
requiredReturns:
Type DescriptionOptional[list[Command]]
Optional[list[Command]] - A list of commands mounted. None if the module's setup() was called.
Raises:
Type DescriptionImportError
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 Defaultmodule
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 Defaultname
str
The name of the command to retrieve
requiredReturns:
Type DescriptionOptional[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_nicknameasync
","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 Defaultroom
Union[str, MatrixRoom]
The room to change the nickname in.
requirednew_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 DescriptionRoomPutStateResponse
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_messageasync
","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_messageasync
","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 Defaultroom_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 DescriptionOptional[Tuple[MatrixRoom, RoomMessage]]
The room and message that was received.
"},{"location":"reference/client/#niobot.client.NioBot.markdown_to_html","title":"markdown_to_htmlasync
staticmethod
","text":"markdown_to_html(text: str) -> str\n
Converts markdown to HTML.
Parameters:
Name Type Description Defaulttext
str
The markdown to render as HTML
requiredReturns:
Type Descriptionstr
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_roomsasync
","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 Defaultuser
Optional[Union[MatrixUser, str]]
The user ID or object to get DM rooms for.
None
Returns:
Type DescriptionUnion[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_roomasync
","text":"create_dm_room(\n user: Union[MatrixUser, str]\n) -> RoomCreateResponse\n
Creates a DM room with a given user.
Parameters:
Name Type Description Defaultuser
Union[MatrixUser, str]
The user to create a DM room with.
requiredReturns:
Type DescriptionRoomCreateResponse
The response from the server.
"},{"location":"reference/client/#niobot.client.NioBot.send_message","title":"send_messageasync
","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.
DMsAs 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 Defaultroom
Union[MatrixRoom, MatrixUser, str]
The room or to send this message to
requiredcontent
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 DescriptionRoomSendResponse
The response from the server.
Raises:
Type DescriptionMessageException
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_messageasync
","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 Defaultroom
Union[MatrixRoom, str]
The room the message is in.
requiredmessage
Union[Event, str]
The message to edit.
requiredcontent
str
The new content of the message.
requiredmessage_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 DescriptionRuntimeError
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_messageasync
","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 Defaultroom
Union[MatrixRoom, str]
The room the message is in.
requiredmessage_id
Union[RoomMessage, str]
The message to delete.
requiredreason
Optional[str]
The reason for deleting the message.
None
Raises:
Type DescriptionRuntimeError
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_reactionasync
","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 Defaultroom
Union[MatrixRoom, str]
The room the message is in.
requiredmessage
Union[RoomMessage, str]
The event ID or message object to react to.
requiredemoji
str
The emoji to react with (e.g. \u274c
= \u274c)
Returns:
Type DescriptionRoomSendResponse
The response from the server.
Raises:
Type DescriptionMessageException
If the message fails to react.
"},{"location":"reference/client/#niobot.client.NioBot.redact_reaction","title":"redact_reactionasync
","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":"startasync
","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 Defaultpassword
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 DescriptionNone
"},{"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 Defaultkey
str
the key to get
requiredroom_id
str
The room ID to get account data from. If not provided, defaults to user-level.
None
Returns:
Type DescriptionUnion[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_dataasync
","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 Defaultkey
str
the key to set
requireddata
dict
the data to set
requiredroom_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 Defaultroom_id
str
The room ID or alias to join
requiredreason
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.
Examplefrom 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 Defaultname
str
The name of the argument. Will be used to know which argument to pass to the command callback.
requiredarg_type
_T
The type of the argument (e.g. str, int, etc. or a custom type)
requireddescription
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.
ExampleNote
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 Defaultname
str
The name of the command. Will be used to invoke the command.
requiredcallback
Callable
The callback to call when the command is invoked.
requiredaliases
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_argsstaticmethod
","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 Defaultcallback
The function to inspect
requiredReturns:
Type Descriptionlist[Argument]
A list of arguments. self
, and ctx
are skipped.
__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_runasync
","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_argsasync
","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":"invokeasync
","text":"invoke(ctx: Context) -> Coroutine\n
Invokes the current command with the given context
Parameters:
Name Type Description Defaultctx
Context
The current context
requiredRaises:
Type DescriptionCommandArgumentsError
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 Defaultclient
NioBot
The current instance of the client.
requiredroom
MatrixRoom
The room the command was invoked in.
requiredsrc_event
RoomMessageText
The source event that triggered the command. Must be nio.RoomMessageText
.
invoking_prefix
str
The prefix that triggered the command.
requiredmeta
str
The invoking string (usually the command name, however may be an alias instead)
requiredcls
type
The class to construct the context with. Defaults to Context
.
Context
Returns:
Type DescriptionContext
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 Descriptionbot
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\"]
.
__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 Defaultname
Optional[str]
The name of the command. Defaults to function.name
None
kwargs
Any key-words to pass to Command
{}
Returns:
Type DescriptionCallable
"},{"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 Defaultfunction
Callable[[Context], Union[bool, Coroutine[None, None, bool]]]
The function to register as a check
requiredReturns:
Type DescriptionCallable
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 Defaultname
Optional[Union[str, Event]]
the name of the event (no on_
prefix)
None
Returns:
Type DescriptionCallable
"},{"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.Event-based context for a command callback
"},{"location":"reference/context/#niobot.context.Context.room","title":"roomproperty
","text":"room: MatrixRoom\n
The room that the event was dispatched in
"},{"location":"reference/context/#niobot.context.Context.client","title":"clientproperty
","text":"client: 'NioBot'\n
The current instance of the client
"},{"location":"reference/context/#niobot.context.Context.command","title":"commandproperty
","text":"command: 'Command'\n
The current command being invoked
"},{"location":"reference/context/#niobot.context.Context.args","title":"argsproperty
","text":"args: list[str]\n
Each argument given to this command
"},{"location":"reference/context/#niobot.context.Context.message","title":"messageproperty
","text":"message: RoomMessageText\n
The current message
"},{"location":"reference/context/#niobot.context.Context.original_response","title":"original_responseproperty
","text":"original_response: Optional[RoomSendResponse]\n
The result of Context.reply(), if it exists.
"},{"location":"reference/context/#niobot.context.Context.latency","title":"latencyproperty
","text":"latency: float\n
Returns the current event's latency in milliseconds.
"},{"location":"reference/context/#niobot.context.Context.respond","title":"respondasync
","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_eventasync
","text":"original_event() -> Optional[RoomMessage]\n
Fetches the current event for this response
"},{"location":"reference/context/#niobot.context.ContextualResponse.reply","title":"replyasync
","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":"editasync
","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":"deleteasync
","text":"delete(reason: Optional[str] = None) -> None\n
Redacts the current response.
Parameters:
Name Type Description Defaultreason
Optional[str]
An optional reason for the redaction
None
Returns:
Type DescriptionNone
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.
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.
Exampleimport 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 moduleimport 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 Defaultresult
SyncResponse
The response from the sync.
required"},{"location":"reference/events/#niobot._event_stubs.message","title":"messageasync
","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 Defaultroom
MatrixRoom
The room that the message was received in.
requiredevent
RoomMessage
The raw event that triggered the message.
required"},{"location":"reference/events/#niobot._event_stubs.command","title":"commandasync
","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 Defaultctx
Context
The context of the command.
required"},{"location":"reference/events/#niobot._event_stubs.command_complete","title":"command_completeasync
","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 Defaultctx
Context
The context of the command.
requiredresult
Any
The result of the command (the returned value of the callback)
required"},{"location":"reference/events/#niobot._event_stubs.command_error","title":"command_errorasync
","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 errorAs 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 Defaultctx
Context
The context of the command.
requirederror
CommandError
The error that was raised.
required"},{"location":"reference/events/#niobot._event_stubs.raw","title":"rawasync
","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.
ExampleThe 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.
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 Descriptionmessage
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 Defaultother
Optional[Union[Exception, ErrorResponse]]
The other exception to recurse down. If None, defaults to the exception this method is called on.
None
Returns:
Type DescriptionUnion[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:
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.
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)
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 Defaultextra_owner_ids
A set of @localpart:homeserver.tld
strings to check against.
()
Returns:
Type DescriptionTrue - the check passed.
Raises:
Type DescriptionNotOwner
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 Defaultallow_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 Defaultlevel
int
The minimum power level
requiredroom_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 Defaultlevel
int
The minimum power level
requiredReturns:
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 Defaulthomeservers
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_homeserverasync
","text":"resolve_homeserver(domain: str) -> str\n
Resolves a given homeserver part to the actual homeserver
Parameters:
Name Type Description Defaultdomain
str
The domain to crawl
requiredReturns:
Type Descriptionstr
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.
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:
<brackets>
, optional in [brackets]
)The command is only listed if:
disabled=True
is passed, or omitted entirely)hidden=True
is not passed (or is ommitted entirely))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:
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_outputstaticmethod
","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 Defaulttext
str
The text to sanitise
requiredescape_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 Descriptionstr
The cleaned text
"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.format_command_name","title":"format_command_namestaticmethod
","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_descriptionstaticmethod
","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_descriptionstaticmethod
","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_helpasync
","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":"respondasync
","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.
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 Defaultroom
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_idsinstance-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: []}}
)
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:
NioBot.default_parse_mentions
is True
(default) ANDNioBot.send_message
is not given an explicit mentions=
argument ANDcontent
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.
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.
These are a handful of built-in parsers that you can use with niobot.Argument
.
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":"parseclassmethod
","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 Defaultctx
Context
The context instance
requiredarg
Argument
The argument instance
requiredvalue
str
The value to parse
requiredReturns:
Type Descriptiontyping.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 Descriptionbool
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 Descriptionfloat
A parsed floating point number
Raises:
Type DescriptionCommandParserError
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 Defaultallow_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 DescriptionUnion[int, float]
A parsed integer or float, depending on input & allow_floats
Raises:
Type DescriptionCommandParserError
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 DescriptionUnion[dict, list, str, int, float, None, bool]
The parsed JSON object
Raises:
Type DescriptionCommandParserError
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 asyncThis parser is async, and should be awaited when used manually.
Returns:
Type Descriptionnio.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 Defaultevent_type
Optional[str]
The event type to expect (such as m.room.message). If None, any event type is allowed.
None
Returns:
Type Descriptiontyping.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 Defaultdomain
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 Descriptiontyping.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 DescriptionMatrixMXCUrl (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:
niobot.Context
as its first argumentniobot.Argument
as its second argumentstr
ing (the user's input) as its third argumentDo 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 Defaultstring
str
The string to parse
required"},{"location":"reference/utils/string_view/#niobot.utils.string_view.ArgumentView.eof","title":"eofproperty
","text":"eof: bool\n
Returns whether the parser has reached the end of the string
Returns:
Type Descriptionbool
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 Defaultargument
str
The argument to add
requiredReturns:
Type DescriptionNone
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 DescriptionArgumentView
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.
Context manager to manage typing notifications.
Parameters:
Name Type Description Defaultclient
NioBot
The NioBot
instance
room_id
str
The room id to send the typing notification to
requiredtimeout
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.
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.
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)
.
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.
Exampleimport 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 Defaultfunction
Callable[..., T]
The function to call. Make sure you do not call it, just pass it.
requiredargs
Any
The arguments to pass to the function.
()
kwargs
Any
The keyword arguments to pass to the function.
{}
Returns:
Type DescriptionT
The result of the function.
"},{"location":"reference/utils/unblock/#niobot.utils.unblocking.force_await","title":"force_awaitasync
","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 Defaultfunction
Union[Callable, Coroutine]
The function to call. Make sure you do not call it, just pass it.
requiredargs
Any
The arguments to pass to the function.
()
kwargs
Any
The keyword arguments to pass to the function.
{}
Returns:
Type DescriptionThe 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.
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.
NioBot contains all of the features of matrix-nio, plus a few more:
[niobot.Context][]
object, given to every command.[niobot.NioBot][]
, and you're ready to go.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!
force_write
properly<instance> has no attribute 'mro'
error when initialising auto-detected argumentsniocli get-access-token
crashing on windowsNioBot
throwing a warning about failing key uploads without logging the actual errorcommand_prefix
to be an iterable, converting single-strings into a single-item iterable too.message
event to be any nio.RoomMessage, not just Text
.xyzamorganblurhash
into ImageAttachment
automatic_markdown_parser
option and functionality in NioBotniobot.utils.parsers.EventParser
raising an error when usedname
parameter from niobot checksnio.Event
types to event listenerstyping.Optional
in automatic argument detection*args
in automatic argument detectionnull
for metadata, which may cause incorrect client behavioursniobot.utils.Mentions
to handle intentional mentions in messagesniobot.util.help_command.help_command_callback
was removed, in line with deprecation./_matrix/client/versions
to fetch server version metadata.niobot.utils.help_command.DefaultHelpCommand
, to make subclassing easier.default_help_command
was replaced with DefaultHelpCommand().respond
.Command.can_run(ctx)
, which runs through and makes sure that all of the command checks pass.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)sync_full_state=False
.niobot.NioBot.join
throwing a JSON EOF in some casesreason
parameter to niobot.NioBot.join
and niobot.NioBot.room_leave
as optional stringsniobot.NioBot.send_message
will now automatically parse mentions if not explicitly provided, to take full advantage of intentional mentions.force_initial_sync
to niobot.NioBot
, which will force the bot to sync all rooms before starting the event loop.force_write
parameter in some BaseAttachment
(and subclass) methods. (2024-06-15)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)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.NioBot.get_dm_rooms
raising a 401 Unauthorised error regardless of any state.NioBot.get_dm_rooms
raising a GenericMatrixError
whenever there were no DM rooms, instead of gracefully returning an empty object.NioBot.get_dm_rooms
using outdated code from before matrix-nio==0.24.0
.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 versionsThis 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":"niobot.Context.invoking_prefix
.niobot.NioBot.is_ready
, which is an asyncio.Event
.@niobot.check
)GenericMatrixError
.GPLv3
to LGPLv3
.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.ignore_self
flag to niobot.NioBot
, allowing you to choose whether the client will ignore its own messages.typing.Annotated
in commands.niobot.Module.log
was fully removed - it was never fully functional and often tripped up users as it was unsettable.niobot.Module.client
was deprecated - you should use niobot.Module.client
instead.event_id
over room_id
for the _get_id
functionContext.invoking_prefix
niobot
with nio-bot
for pip install guide.niobot.attachments.which
function.event_parser
and room_parser
force_await
now just awaits coroutines rather than casting them to a taskThis 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":"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.niobot.ImageAttachment
being unable to detect image streams.niobot.BaseAttachment
setting incorrect file propertiesniobot.ImageAttachment
no longer explicitly fails upon encountering an unknown format, it simply emits a warning, in line with niobot.VideoAttachment
.setup.py
for really old pip versions.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
.Command(arguments=[...])
.__repr__
to most objects in the library.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:
And the following installed on the machine you want to run the bot on:
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.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:
config.py
file to store our configuration.main.py
file to store our bot code.fun.py
file to store a module (later on).And you'll need a directory:
store
- this is where nio-bot will store its database and other files.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.
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.
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
.
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}!\"
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
.
sso_token
? SSO token is a S
ingle S
ign O
n 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.
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:
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.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.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.
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!
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/UbuntuArchFedoramacOSWindowssudo 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: 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.
<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.
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:
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 imagesWhile 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 experienceDisabling 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:
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!
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.
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
.
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:
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:
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:
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:
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.
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 DescriptionFILE
'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 Defaultfile
Union[str, BytesIO, PathLike, Path]
The file path or BytesIO object to upload.
requiredfile_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 Descriptionfile
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_bytesproperty
","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 Defaultbody
Optional[str]
The body to use (should be a textual description). Defaults to the file name.
None
Returns:
Type Descriptiondict
"},{"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 Defaultfile
Union[str, BytesIO, Path]
The file or BytesIO to attach
requiredfile_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_mxcasync
classmethod
","text":"from_mxc(client: 'NioBot', url: str) -> 'BaseAttachment'\n
Creates an attachment from an MXC URL.
Parameters:
Name Type Description Defaultclient
'NioBot'
The current client instance (used to download the attachment)
requiredurl
str
The MXC:// url to download
requiredReturns:
Type Description'BaseAttachment'
The downloaded and probed attachment.
"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.from_http","title":"from_httpasync
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 Defaulturl
str
The http/s URL to download
requiredclient_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 Descriptionniobot.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 Defaultunit
Literal['b', 'kb', 'kib', 'mb', 'mib', 'gb', 'gib']
The unit to convert into
requiredReturns:
Type DescriptionUnion[int, float]
The converted size
"},{"location":"reference/attachment/#niobot.attachment.BaseAttachment.upload","title":"uploadasync
","text":"upload(\n client: \"NioBot\", encrypted: bool = False\n) -> \"BaseAttachment\"\n
Uploads the file to matrix.
Parameters:
Name Type Description Defaultclient
'NioBot'
The client to upload
requiredencrypted
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 Defaultfile
Union[str, BytesIO, Path]
The file to upload
requiredfile_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 Defaultfile
Union[str, BytesIO, Path]
The file to upload
requiredfile_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 Descriptioninfo
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":"infoproperty
","text":"info: Dict\n
returns the info dictionary for this image.
Use ImageAttachment.as_body()[\"info\"]
instead.
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 Defaultfile
Union[str, BytesIO, Path]
The file to upload
requiredfile_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_imagestaticmethod
","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 Defaultimage
Union[Image, BytesIO, str, Path]
The image to thumbnail
requiredsize
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 DescriptionImage
The thumbnail
"},{"location":"reference/attachment/#niobot.attachment.ImageAttachment.get_blurhash","title":"get_blurhashasync
","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 Defaultquality
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 Descriptionstr
The blurhash
"},{"location":"reference/attachment/#niobot.attachment.VideoAttachment","title":"VideoAttachment","text":" Bases: BaseAttachment
Represents a video attachment.
Parameters:
Name Type Description Defaultfile
Union[str, BytesIO, Path]
The file to upload
requiredfile_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 Defaultfile
Union[str, BytesIO, Path]
The file to upload
requiredfile_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_thumbnailasync
staticmethod
","text":"generate_thumbnail(\n video: Union[str, Path, \"VideoAttachment\"]\n) -> ImageAttachment\n
Generates a thumbnail for a video.
Parameters:
Name Type Description Defaultvideo
Union[str, Path, 'VideoAttachment']
The video to generate a thumbnail for
requiredReturns:
Type DescriptionImageAttachment
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":"durationproperty
writable
","text":"duration: Optional[int]\n
The duration of this audio in milliseconds
"},{"location":"reference/attachment/#niobot.attachment.AudioAttachment.from_file","title":"from_fileasync
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 Defaultfile
Union[str, BytesIO, Path]
The file to upload
requiredfile_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 Defaultfile
Union[str, BytesIO, Path]
The file to detect the mime type of. Can be a BytesIO.
requiredReturns:
Type Descriptionstr
The mime type of the file (e.g. text/plain
, image/png
, application/pdf
, video/webp
etc.)
Raises:
Type DescriptionRuntimeError
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 Defaultfile
Union[str, Path]
The file to get metadata for. Must be a path-like object
requiredReturns:
Type Descriptiondict[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 Defaultfile
Path
The file to get metadata for. Must be a path object
requiredReturns:
Type Descriptiondict[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 Defaultfile
Union[str, Path]
The file to get metadata for.
requiredmime_type
Optional[str]
The mime type of the file. If not provided, it will be detected.
None
Returns:
Type Descriptiondict[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 Defaultfile
Union[str, Path]
The file to get the first frame of. Must be a path-like object
requiredfile_format
str
The format to save the frame as. Defaults to webp.
'webp'
Returns:
Type Descriptionbytes
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
.
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 Defaultfile
Union[BytesIO, Path, str]
The file or BytesIO to investigate
requiredmime_type
Optional[str]
The optional pre-detected mime type. If this is not provided, it will be detected.
None
Returns:
Type DescriptionUnion[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 Defaulthomeserver
str
The homeserver to connect to. e.g. https://matrix-client.matrix.org
requireduser_id
str
The user ID to log in as. e.g. @user:matrix.org
requireddevice_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.
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.
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
)
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_httpasync
","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 Defaultmxc
str
The mxc URI
requiredhomeserver
Optional[str]
The homeserver to download this through (defaults to the bot's homeserver)
None
Returns:
Type DescriptionOptional[str]
an MXC URL, if applicable
"},{"location":"reference/client/#niobot.client.NioBot.latency","title":"latencystaticmethod
","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 Defaultevent
Event
The event to measure latency with
requiredreceived_at
Optional[float]
The optional time the event was received at. If not given, uses the current time.
None
Returns:
Type Descriptionfloat
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_receiptsasync
","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 Defaultroom
Union[str, MatrixRoom]
The room to update the read receipt in.
requiredevent
Event
The event to move the read receipt to.
requiredReturns:
Type DescriptionNothing
"},{"location":"reference/client/#niobot.client.NioBot.process_message","title":"process_messageasync
","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 Defaultuser_id
str
The user ID to check.
requiredReturns:
Type Descriptionbool
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 Defaultimport_path
str
The import path (such as modules.file), which would be ./modules/file.py in a file tree.
requiredReturns:
Type DescriptionOptional[list[Command]]
Optional[list[Command]] - A list of commands mounted. None if the module's setup() was called.
Raises:
Type DescriptionImportError
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 Defaultmodule
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 Defaultname
str
The name of the command to retrieve
requiredReturns:
Type DescriptionOptional[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_nicknameasync
","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 Defaultroom
Union[str, MatrixRoom]
The room to change the nickname in.
requirednew_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 DescriptionRoomPutStateResponse
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_messageasync
","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_messageasync
","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 Defaultroom_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 DescriptionOptional[Tuple[MatrixRoom, RoomMessage]]
The room and message that was received.
"},{"location":"reference/client/#niobot.client.NioBot.markdown_to_html","title":"markdown_to_htmlasync
staticmethod
","text":"markdown_to_html(text: str) -> str\n
Converts markdown to HTML.
Parameters:
Name Type Description Defaulttext
str
The markdown to render as HTML
requiredReturns:
Type Descriptionstr
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_roomsasync
","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 Defaultuser
Optional[Union[MatrixUser, str]]
The user ID or object to get DM rooms for.
None
Returns:
Type DescriptionUnion[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_roomasync
","text":"create_dm_room(\n user: Union[MatrixUser, str]\n) -> RoomCreateResponse\n
Creates a DM room with a given user.
Parameters:
Name Type Description Defaultuser
Union[MatrixUser, str]
The user to create a DM room with.
requiredReturns:
Type DescriptionRoomCreateResponse
The response from the server.
"},{"location":"reference/client/#niobot.client.NioBot.send_message","title":"send_messageasync
","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.
DMsAs 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 Defaultroom
Union[MatrixRoom, MatrixUser, str]
The room or to send this message to
requiredcontent
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 DescriptionRoomSendResponse
The response from the server.
Raises:
Type DescriptionMessageException
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_messageasync
","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 Defaultroom
Union[MatrixRoom, str]
The room the message is in.
requiredmessage
Union[Event, str]
The message to edit.
requiredcontent
str
The new content of the message.
requiredmessage_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 DescriptionRuntimeError
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_messageasync
","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 Defaultroom
Union[MatrixRoom, str]
The room the message is in.
requiredmessage_id
Union[RoomMessage, str]
The message to delete.
requiredreason
Optional[str]
The reason for deleting the message.
None
Raises:
Type DescriptionRuntimeError
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_reactionasync
","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 Defaultroom
Union[MatrixRoom, str]
The room the message is in.
requiredmessage
Union[RoomMessage, str]
The event ID or message object to react to.
requiredemoji
str
The emoji to react with (e.g. \u274c
= \u274c)
Returns:
Type DescriptionRoomSendResponse
The response from the server.
Raises:
Type DescriptionMessageException
If the message fails to react.
"},{"location":"reference/client/#niobot.client.NioBot.redact_reaction","title":"redact_reactionasync
","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":"startasync
","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 Defaultpassword
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 DescriptionNone
"},{"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 Defaultkey
str
the key to get
requiredroom_id
str
The room ID to get account data from. If not provided, defaults to user-level.
None
Returns:
Type DescriptionUnion[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_dataasync
","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 Defaultkey
str
the key to set
requireddata
dict
the data to set
requiredroom_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 Defaultroom_id
str
The room ID or alias to join
requiredreason
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.
Examplefrom 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 Defaultname
str
The name of the argument. Will be used to know which argument to pass to the command callback.
requiredarg_type
_T
The type of the argument (e.g. str, int, etc. or a custom type)
requireddescription
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.
ExampleNote
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 Defaultname
str
The name of the command. Will be used to invoke the command.
requiredcallback
Callable
The callback to call when the command is invoked.
requiredaliases
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_argsstaticmethod
","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 Defaultcallback
The function to inspect
requiredReturns:
Type Descriptionlist[Argument]
A list of arguments. self
, and ctx
are skipped.
__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_runasync
","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_argsasync
","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":"invokeasync
","text":"invoke(ctx: Context) -> Coroutine\n
Invokes the current command with the given context
Parameters:
Name Type Description Defaultctx
Context
The current context
requiredRaises:
Type DescriptionCommandArgumentsError
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 Defaultclient
NioBot
The current instance of the client.
requiredroom
MatrixRoom
The room the command was invoked in.
requiredsrc_event
RoomMessageText
The source event that triggered the command. Must be nio.RoomMessageText
.
invoking_prefix
str
The prefix that triggered the command.
requiredmeta
str
The invoking string (usually the command name, however may be an alias instead)
requiredcls
type
The class to construct the context with. Defaults to Context
.
Context
Returns:
Type DescriptionContext
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 Descriptionbot
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\"]
.
__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 Defaultname
Optional[str]
The name of the command. Defaults to function.name
None
kwargs
Any key-words to pass to Command
{}
Returns:
Type DescriptionCallable
"},{"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 Defaultfunction
Callable[[Context], Union[bool, Coroutine[None, None, bool]]]
The function to register as a check
requiredReturns:
Type DescriptionCallable
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 Defaultname
Optional[Union[str, Event]]
the name of the event (no on_
prefix)
None
Returns:
Type DescriptionCallable
"},{"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.Event-based context for a command callback
"},{"location":"reference/context/#niobot.context.Context.room","title":"roomproperty
","text":"room: MatrixRoom\n
The room that the event was dispatched in
"},{"location":"reference/context/#niobot.context.Context.client","title":"clientproperty
","text":"client: 'NioBot'\n
The current instance of the client
"},{"location":"reference/context/#niobot.context.Context.command","title":"commandproperty
","text":"command: 'Command'\n
The current command being invoked
"},{"location":"reference/context/#niobot.context.Context.args","title":"argsproperty
","text":"args: list[str]\n
Each argument given to this command
"},{"location":"reference/context/#niobot.context.Context.message","title":"messageproperty
","text":"message: RoomMessageText\n
The current message
"},{"location":"reference/context/#niobot.context.Context.original_response","title":"original_responseproperty
","text":"original_response: Optional[RoomSendResponse]\n
The result of Context.reply(), if it exists.
"},{"location":"reference/context/#niobot.context.Context.latency","title":"latencyproperty
","text":"latency: float\n
Returns the current event's latency in milliseconds.
"},{"location":"reference/context/#niobot.context.Context.respond","title":"respondasync
","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_eventasync
","text":"original_event() -> Optional[RoomMessage]\n
Fetches the current event for this response
"},{"location":"reference/context/#niobot.context.ContextualResponse.reply","title":"replyasync
","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":"editasync
","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":"deleteasync
","text":"delete(reason: Optional[str] = None) -> None\n
Redacts the current response.
Parameters:
Name Type Description Defaultreason
Optional[str]
An optional reason for the redaction
None
Returns:
Type DescriptionNone
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.
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.
Exampleimport 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 moduleimport 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 Defaultresult
SyncResponse
The response from the sync.
required"},{"location":"reference/events/#niobot._event_stubs.message","title":"messageasync
","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 Defaultroom
MatrixRoom
The room that the message was received in.
requiredevent
RoomMessage
The raw event that triggered the message.
required"},{"location":"reference/events/#niobot._event_stubs.command","title":"commandasync
","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 Defaultctx
Context
The context of the command.
required"},{"location":"reference/events/#niobot._event_stubs.command_complete","title":"command_completeasync
","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 Defaultctx
Context
The context of the command.
requiredresult
Any
The result of the command (the returned value of the callback)
required"},{"location":"reference/events/#niobot._event_stubs.command_error","title":"command_errorasync
","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 errorAs 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 Defaultctx
Context
The context of the command.
requirederror
CommandError
The error that was raised.
required"},{"location":"reference/events/#niobot._event_stubs.raw","title":"rawasync
","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.
ExampleThe 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.
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 Descriptionmessage
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 Defaultother
Optional[Union[Exception, ErrorResponse]]
The other exception to recurse down. If None, defaults to the exception this method is called on.
None
Returns:
Type DescriptionUnion[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:
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.
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)
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 Defaultextra_owner_ids
A set of @localpart:homeserver.tld
strings to check against.
()
Returns:
Type DescriptionTrue - the check passed.
Raises:
Type DescriptionNotOwner
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 Defaultallow_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 Defaultlevel
int
The minimum power level
requiredroom_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 Defaultlevel
int
The minimum power level
requiredReturns:
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 Defaulthomeservers
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_homeserverasync
","text":"resolve_homeserver(domain: str) -> str\n
Resolves a given homeserver part to the actual homeserver
Parameters:
Name Type Description Defaultdomain
str
The domain to crawl
requiredReturns:
Type Descriptionstr
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.
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:
<brackets>
, optional in [brackets]
)The command is only listed if:
disabled=True
is passed, or omitted entirely)hidden=True
is not passed (or is ommitted entirely))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:
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_outputstaticmethod
","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 Defaulttext
str
The text to sanitise
requiredescape_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 Descriptionstr
The cleaned text
"},{"location":"reference/utils/help_command/#niobot.utils.help_command.DefaultHelpCommand.format_command_name","title":"format_command_namestaticmethod
","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_descriptionstaticmethod
","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_descriptionstaticmethod
","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_helpasync
","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":"respondasync
","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.
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 Defaultroom
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_idsinstance-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: []}}
)
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:
NioBot.default_parse_mentions
is True
(default) ANDNioBot.send_message
is not given an explicit mentions=
argument ANDcontent
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.
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.
These are a handful of built-in parsers that you can use with niobot.Argument
.
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":"parseclassmethod
","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 Defaultctx
Context
The context instance
requiredarg
Argument
The argument instance
requiredvalue
str
The value to parse
requiredReturns:
Type Descriptiontyping.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 Descriptionbool
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 Descriptionfloat
A parsed floating point number
Raises:
Type DescriptionCommandParserError
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 Defaultallow_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 DescriptionUnion[int, float]
A parsed integer or float, depending on input & allow_floats
Raises:
Type DescriptionCommandParserError
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 DescriptionUnion[dict, list, str, int, float, None, bool]
The parsed JSON object
Raises:
Type DescriptionCommandParserError
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 asyncThis parser is async, and should be awaited when used manually.
Returns:
Type Descriptionnio.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 Defaultevent_type
Optional[str]
The event type to expect (such as m.room.message). If None, any event type is allowed.
None
Returns:
Type Descriptiontyping.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 Defaultdomain
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 Descriptiontyping.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 DescriptionMatrixMXCUrl (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/#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:
niobot.Context
as its first argumentniobot.Argument
as its second argumentstr
ing (the user's input) as its third argumentDo 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 Defaultstring
str
The string to parse
required"},{"location":"reference/utils/string_view/#niobot.utils.string_view.ArgumentView.eof","title":"eofproperty
","text":"eof: bool\n
Returns whether the parser has reached the end of the string
Returns:
Type Descriptionbool
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 Defaultargument
str
The argument to add
requiredReturns:
Type DescriptionNone
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 DescriptionArgumentView
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.
Context manager to manage typing notifications.
Parameters:
Name Type Description Defaultclient
NioBot
The NioBot
instance
room_id
str
The room id to send the typing notification to
requiredtimeout
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.
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.
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)
.
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.
Exampleimport 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 Defaultfunction
Callable[..., T]
The function to call. Make sure you do not call it, just pass it.
requiredargs
Any
The arguments to pass to the function.
()
kwargs
Any
The keyword arguments to pass to the function.
{}
Returns:
Type DescriptionT
The result of the function.
"},{"location":"reference/utils/unblock/#niobot.utils.unblocking.force_await","title":"force_awaitasync
","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 Defaultfunction
Union[Callable, Coroutine]
The function to call. Make sure you do not call it, just pass it.
requiredargs
Any
The arguments to pass to the function.
()
kwargs
Any
The keyword arguments to pass to the function.
{}
Returns:
Type DescriptionThe result of the function.
"}]} \ No newline at end of file diff --git a/master/sitemap.xml.gz b/master/sitemap.xml.gz index 7607e6e63288faf0a885e256820b6f15379cc7ae..d7c33b061138be7b31fba75dfffbf7efe75417c0 100644 GIT binary patch delta 13 Ucmb=gXP58h;9!t;oXB1Q02dYmEC2ui delta 13 Ucmb=gXP58h;9#h*o5)@P02tc?h5!Hn