Git Product home page Git Product logo

nio-bot's Introduction

Nio-Bot

Making Matrix bots simple

GitHub issues GitHub pull requests GitHub GitHub Repo stars

PyPI - Downloads PyPI - Version Python Version from PEP 621 TOML

Matrix


Installing

You can install the latest stable release from PyPi:

pip install nio-bot
# Or for cutting-edge releases:
# pip install --pre nio-bot

You may also want some extras:

  • End-to-End encryption support: nio-bot[e2ee]
  • The CLI (recommended): nio-bot[cli]
  • Both: nio-bot[cli,e2ee]
  • Development dependencies: nio-bot[dev]

Please note that e2ee uses olm, which depends on libolm. You can likely install this though your system package manager.

Features

Nio-Bot is built on the solid client library, matrix-nio. This means that you get the full experience of a Matrix client, with the added benefit of being able to easily create bots.

Nio-Bot comes with a whole host of features to help make your development experience as easy as possible. Some features include, but are not limited to:

  • A powerful commands framework (Modules, aliases, checks, easy extensibility)
  • Custom argument parser support
  • A flexible event system
  • Simple end-to-end encryption
  • Automatic markdown rendering when sending/editing messages
  • Super simple to use Attachments system
  • Very customisable monolithic client instance
  • A simple, easy-to-use CLI tool for some on-the-go tasks
  • Full attachment support (File, Image, Audio, Video), with encryption support
  • In-depth, simple, clean documentation

Help

You can join our Matrix Room for help, or to just chat. You can also get the latest updates in development there, including having your say in how new things are implemented!

A quick example

# This example was written using version 1.1.0.
import niobot

client = niobot.NioBot(
    # Note that all of these options other than the following are optional:
    # * homeserver
    # * user_id
    # * command_prefix
    homeserver="https://matrix.example.org",  # it is important that you use the matrix server, not the delegation URL
    user_id="@example1:example.org",
    device_id="my-device-name",
    command_prefix="!",
    case_insensitive=True,
    owner_id="@example2:example.org",
    ignore_self=True  # default is True, set to false to not ignore the bot's own messages
)

@client.on_event("ready")
async def on_ready(sync_result: niobot.SyncResponse):
    print("Logged in!")


# A simple command
@client.command()
async def ping(ctx: niobot.Context):
    latency = ctx.latency
    await ctx.respond(f"Pong! {latency:.2f}ms")


# A command with arguments
@client.command()
async def echo(ctx: niobot.Context, *, message: str):
    await ctx.respond("You said: " + message)


client.run(access_token="...")

Using the CLI to get an access token

If you install the cli extras, you can use niocli to get an access token from a username and password (read this for why you'd want to use an access token):

niocli get-access-token -U '@example1:example.org' -D 'my-device-name'

After putting in your password, an access token will be printed to the console once the login is successful.

Further reading

Look at the docs for more information on how to use Nio-Bot, or the examples on github.

nio-bot's People

Contributors

dependabot[bot] avatar fvjosef21 avatar jodhus avatar matthieu-laurent39 avatar moriango avatar nexy7574 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

nio-bot's Issues

How to verify bot session

I have used provided example to create a simple bot. But can't get bot verified. I have tried to put this stuff together at 4am, so maybe missed something.

import niobot
import logging

logging.basicConfig(level=logging.INFO)

client = niobot.NioBot(
    homeserver="https://matrix.org",
    user_id="@<me>:matrix.org",
    device_id="container-svc",
    store_path='./store',
    command_prefix="!",
    case_insensitive=True,
)

@client.on_event("ready")
async def on_ready(sync_result: niobot.SyncResponse):
    print("Logged in!")

@client.on_event("message")
async def on_message(room: niobot.MatrixRoom, message: niobot.RoomMessage):
    print(message)

@client.command()
async def ping(ctx: niobot.Context):
    latency = ctx.latency
    await ctx.respond(f"Pong! {latency:.2f}ms")

@client.command()
async def echo(ctx: niobot.Context, *, message: str):
    await ctx.respond("You said: " + message)

client.run(access_token="<my token>")

Here is some logs where I have received key verification event, but there are no docs/examples how to handle it.

INFO:niobot.client:Encryption support enabled automatically.
INFO:niobot.client:Auto-joining rooms enabled.
INFO:niobot.client:Auto-updating read receipts enabled.
INFO:niobot.client:Logging in with existing access token.
INFO:niobot.client:Performing first sync...
...
INFO:niobot.client:Starting sync loop
Logged in!
---> WARNING:nio.crypto.log:Received key verification event with an unknown transaction id from @<me>:matrix.org <---
...

All my messages that I send from bot are like these.
image

image

[BUG] niocli get-access-token will instantly trigger an error

see attached logs

 niocli get-access-token                                                                                                                                                                                                                Traceback (most recent call last):
  File "/usr/local/bin/niocli", line 8, in <module>
    sys.exit(cli_root())
             ^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/click/core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: get_access_token() got an unexpected keyword argument 'output'
niocli version                                                                                                                                                                                                                         NioBot version: 1.1.0.post3.dev11 (v1.1.0, build 11, pre N/A, post 3, commit gd7028e5)
matrix-nio version: 0.24.0
Python version: 3.11.2
Python implementation: CPython
Operating System: Linux-6.6.20+rpt-rpi-v8-aarch64-with-glibc2.36 (Debian GNU/Linux/12 (bookworm) - Debian GNU/Linux 12 (bookworm))
Architecture: aarch64
OLM Installed: Yes
Free Disk Space: / (91.29%)

[BUG] Example Not Working

Describe the bug
Trying to get the example in the docs to work. The bot can connect and it gets all messages/commands, but then proceeds to do nothing.

To Reproduce
Run the example bot in the docs.

Expected behavior
The example to run as stated

Tracebacks (if applicable)
I turned on debug logging and here's the output--I censored my server for privacy reasons:

INFO:nio.rooms:Room !LyTfgcsvcZiCnTlVwr:*****.*** handling event of type RoomMessageText
DEBUG:niobot.client:'message' is not in registered events: {'ready': [<function on_ready at 0x7225d1a2c360>]}
DEBUG:niobot.client:'command' is not in registered events: {'ready': [<function on_ready at 0x7225d1a2c360>]}
DEBUG:niobot.client:Running command ping with context <Context room=<nio.rooms.MatrixRoom object at 0x7225d14bd520> event=RoomMessageText(source={'type': 'm.room.message', 'content': {'msgtype': 'm.text', 'body': '!ping', 'm.mentions': {}}, 'room_id': '!LyTfgcsvcZiCnTlVwr:*****.***', 'event_id': '$0HTSNBn1DVPDkTLXCKC2U1x4k9Og6u_UrnXk280dUNk', 'sender': '@jodhus:*****.***', 'origin_server_ts': 1719001025992}, event_id='$0HTSNBn1DVPDkTLXCKC2U1x4k9Og6u_UrnXk280dUNk', sender='@jodhus:*****.***', server_timestamp=1719001025992, decrypted=True, verified=False, sender_key='7+GovOJq7BrQkkap+ltqpHV3bU5g+TFWIL/6N+RQBWw', session_id='7TjeJlVfKiPpVcXrh71a0XYQxGHOZFDAtmYoknZaxD4', transaction_id=None, body='!ping', formatted_body=None, format=None) command=<Command name='ping' aliases=[] disabled=False>>
DEBUG:niobot.commands:Arguments to pass: [<Context room=<nio.rooms.MatrixRoom object at 0x7225d14bd520> event=RoomMessageText(source={'type': 'm.room.message', 'content': {'msgtype': 'm.text', 'body': '!ping', 'm.mentions': {}}, 'room_id': '!LyTfgcsvcZiCnTlVwr:*****.***', 'event_id': '$0HTSNBn1DVPDkTLXCKC2U1x4k9Og6u_UrnXk280dUNk', 'sender': '@jodhus:*****.***', 'origin_server_ts': 1719001025992}, event_id='$0HTSNBn1DVPDkTLXCKC2U1x4k9Og6u_UrnXk280dUNk', sender='@jodhus:*****.***', server_timestamp=1719001025992, decrypted=True, verified=False, sender_key='7+GovOJq7BrQkkap+ltqpHV3bU5g+TFWIL/6N+RQBWw', session_id='7TjeJlVfKiPpVcXrh71a0XYQxGHOZFDAtmYoknZaxD4', transaction_id=None, body='!ping', formatted_body=None, format=None) command=<Command name='ping' aliases=[] disabled=False>>]
DEBUG:niobot.client:'command_error' is not in registered events: {'ready': [<function on_ready at 0x7225d1a2c360>]}
DEBUG:niobot.client:Command 'ping' finished in 0.00 seconds
DEBUG:niobot.client:Updated read receipts for !LyTfgcsvcZiCnTlVwr:*****.*** to $0HTSNBn1DVPDkTLXCKC2U1x4k9Og6u_UrnXk280dUNk.

Additional context
NioBot version: 1.1.0.post3 (v1.1.0, build N/A, pre N/A, post 3, commit N/A)
matrix-nio version: 0.24.0
Python version: 3.12.3
Python implementation: CPython
Operating System: Linux-6.9.4-artix1-1-x86_64-with-glibc2.39 (Artix Linux/Unknown - Artix Linux)
Architecture: x86_64
OLM Installed: Yes

[BUG] Code is non-compatible with Python 3.9

Describe the bug
The minimal supported version right now is 3.9, but the library uses features from Python 3.10.
While the lack of the function is handled by the library to avoid a crash, it gives some inconsistent results based on Python version, which is undesirable.

To Reproduce
N/A

Expected behavior
The program uses only features from Python 3.9 and earlier, or has a minimal supported version of Python 3.10.

Additional context
The function in question is platform.freedesktop_os_release(), added in Python 3.10, and used on line 142 of __main__.py

[BUG] os.uname() called in CLI even on windows

Describe the bug
os.uname().node is called even on windows, where os.uname() does not exist.

To Reproduce
Run niocli get-access-token on windows

Expected behavior
The platform name is correctly fetched without erroring

Additional context
Reported by: @thinkingsand

[BUG] sync loop silently swallowing errors in event callbacks

Describe the bug
According to logs, the finally clause of NioBot.run is being executed, despite my bot not being told to exit.

To Reproduce
The bug appears to be related to this code - https://github.com/nexy7574/philip/blob/6c9a20396ceb80f2230c3e8f6864a3a20e347bd0/modules/discord_bridge.py#L655-L667

                if not avatar:
                    self.log.debug("Fetching %s avatar from matrix.", message.sender)
                    profile = await self.bot.get_profile(message.sender)
                    if isinstance(profile, nio.ProfileGetResponse):
                        if profile.avatar_url:
                            self.log.debug("Fetching avatar from %s", profile.avatar_url)
                            avatar = await self.bot.mxc_to_http(profile.avatar_url)
                        else:
                            self.log.debug("No avatar found.")
                    else:
                        self.log.warning("Failed to fetch profile for %s", message.sender)
                else:
                    self.log.debug("Already have an avatar")

This is what I get in my logs when this code executes:

2024-02-19 11:34:29 INFO httpx HTTP Request: GET http://localhost:3762/bridge/bind/eek4015:matrix.org "HTTP/1.1 404 Not Found"
2024-02-19 11:34:29 DEBUG philip.discord_bridge No bound account for eek4015:matrix.org
2024-02-19 11:34:29 DEBUG philip.discord_bridge No bound discord account for @eek4015:matrix.org
2024-02-19 11:34:29 DEBUG httpx load_ssl_context verify=True cert=None trust_env=True http2=False
2024-02-19 11:34:29 DEBUG httpx load_verify_locations cafile='/home/nex/Bots/philip/venv/lib/python3.11/site-packages/certifi/cacert.pem'
2024-02-19 11:34:29 DEBUG philip.discord_bridge Have a registered webhook URL. Using it.
2024-02-19 11:34:29 INFO niobot.client Closing http session and logging out.

This is strange, because there should be at least one more DEBUG statement from the avatar resolution code, yet there isn't?

At first, I thought somehow python was confusing httpx.AsyncClient closing after exiting a context manager. But after further debugging, the discord account binds should not be able to be printed if this were the case.
I suspect that somehow, the try/finally is swallowing errors. But wait, try/finally should just propagate the error?
More accurately, I believe the error is being swallowed elsewhere, with the try/finally being nothing more than a symptom. However, I am unsure where.

Expected behavior
Any exceptions that are raised in callbacks (even if not owned by us) should at the very least log the exception, ideally be captured an not bubble up far enough to crash the process.

Tracebacks (if applicable)
Contextually, unavailable. However, these are my logs after commenting out the avatar code blocks:

2024-02-19 11:37:28 DEBUG philip.discord_bridge Have a registered webhook URL. Using it.
2024-02-19 11:37:28 DEBUG philip.discord_bridge Preparing body
2024-02-19 11:37:28 DEBUG philip.discord_bridge Body: {'content': 'OMG', 'username': '@eek4015:matrix.org', 'allowed_mentions': {'parse': ['users'], 'replied_user': True}}
2024-02-19 11:37:28 DEBUG philip.discord_bridge Sending message to discord.

and the rest of the code executes successfully.

Additional context

[BUG] `<type> object has no attribute '__mro__'.` while autodetecting arguments

Describe the bug
AttributeError: 'types.UnionType' object has no attribute '__mro__'. Did you mean: '__or__'? is raised when detecting arguments on occasion, especially with typing.Union and friends.

To Reproduce

import niobot

bot = niobot.NioBot(...)


@bot.command()
async def mycommand(ctx: niobot.Context, optional_arg: str | None = None):
    return await ctx.respond(option_arg or "no argument specified")

bot.run(...)

Expected behavior
The optional_arg is properly initialised as an optional string (or in other cases, a Union)

Tracebacks (if applicable)

Traceback (most recent call last):
  File "/home/nex/PycharmProjects/nonsensebot/app/main.py", line 35, in start
    bot.mount_module(name)
  File "/home/nex/.local/share/virtualenvs/nonsensebot-Pef_Ow-h/lib/python3.12/site-packages/niobot/client.py", line 413, in mount_module
    module = importlib.import_module(import_path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/importlib/__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 995, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "/home/nex/PycharmProjects/nonsensebot/app/modules/yd_dl.py", line 26, in <module>
    class YoutubeDLModule(niobot.Module):
  File "/home/nex/PycharmProjects/nonsensebot/app/modules/yd_dl.py", line 124, in YoutubeDLModule
    @niobot.command()
     ^^^^^^^^^^^^^^^^
  File "/home/nex/.local/share/virtualenvs/nonsensebot-Pef_Ow-h/lib/python3.12/site-packages/niobot/commands.py", line 396, in decorator
    cmd = cls(name, func, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/nex/.local/share/virtualenvs/nonsensebot-Pef_Ow-h/lib/python3.12/site-packages/niobot/commands.py", line 200, in __init__
    self.arguments = self.autodetect_args(self.callback)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/nex/.local/share/virtualenvs/nonsensebot-Pef_Ow-h/lib/python3.12/site-packages/niobot/commands.py", line 255, in autodetect_args
    a = Argument(parameter.name, parameter.annotation)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/nex/.local/share/virtualenvs/nonsensebot-Pef_Ow-h/lib/python3.12/site-packages/niobot/commands.py", line 78, in __init__
    for base in inspect.getmro(self.type):
                ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/inspect.py", line 744, in getmro
    return cls.__mro__
           ^^^^^^^^^^^
AttributeError: 'types.UnionType' object has no attribute '__mro__'. Did you mean: '__or__'?

Additional context
The fix is already easy to implement - replace getmro(self.type) with getmro(type(self.type)) (instances can't have MRO), however I need to investigate possible side effects further

[BUG] `Attachment.from_http`: UnboundLocalError: cannot access local variable 'tempdir'

Describe the bug
An error is raised (on Windows, at least) when using Attachment.from_http: UnboundLocalError: cannot access local variable 'tempdir' where it is not associated with a value

To Reproduce

attachement = await niobot.ImageAttachment.from_http("https://cataas.com/cat")

Expected behavior
The function returns properly

Tracebacks (if applicable)

@thinkingsand:shronk.net|
02:45 AM

Traceback (most recent call last):
  File "E:\Matthew\Documents\GitHub\trump\bott.py", line 55, in sync_truths
    attachement = await niobot.ImageAttachment.from_http(page['card']['image'])
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Matthew\AppData\Local\Programs\Python\Python312\Lib\site-packages\niobot\attachment.py", line 570, in from_http
    return await cls.from_http(url, session, force_write=force_write)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Matthew\AppData\Local\Programs\Python\Python312\Lib\site-packages\niobot\attachment.py", line 594, in from_http
    if os.path.isdir(tempdir):
                     ^^^^^^^
UnboundLocalError: cannot access local variable 'tempdir' where it is not associated with a value

Additional context
Reported by: @thinkingsand

"Ghost rooms" returned by get_dm_rooms raising LocalProtocolError

Is your feature request related to a problem? Please describe.
get_dm_rooms returns a list of rooms. However, this list may be stale (since a user may have left the room, forget room, or ?).
This leads to the bug that trying to connect to the room will lead to a LocalProtocolError

Error: LocalProtocolError('No such room with id !NBwtLCgGNCdtVfASwf:[matrix.org](http://matrix.org/) found.')

At the moment, we picked the first room returned by get_dm_rooms in send_message. I suggested to use the last room, since it looked like the newest rooms were at the end of the list, but now I am not sure if that is actually true (it may depend on the specific matrix server).

Describe the solution you'd like
First of. I am a beginner in Matrix, so take the following with a grain of salt. I don't understand the difference between a DM room and a room that has two users with a visibility of "invite only".

MatrixRoom in matrix-nio has a dict for users and visibility. So I guess we can check if the correct two users are in that room and if the visbility and maybe history_visibility is set correctly. Then filter out the rooms that do not match these criteria.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

No idea what else we can do.

Additional context
Changing the room to the last seems to work for me, but now I have a problem that every message from the bot creates a new room.

Python 3.13 Support Tracker

To avoid the mayhem involved in the lack of python 3.12 release support, 3.13 support will be tracked here.

Timeline

  • 2024-05-08 - Python 3.13.0 beta 1 was released
  • 2024-05-28 - Tested installing nio-bot locally on beta0, Failed to build wheels for blurhash-python, because building a wheel for cffi failed.
  • 2024-08-04 - Python 3.13.0rc1 installs successfully when the pre-release of cffi is explicitly requested at install (pip install --pre nio-bot cffi)

Python release timeline

3.13 development begins: Monday, 2023-05-22
3.13.0 alpha 1: Friday, 2023-10-13
3.13.0 alpha 2: Wednesday, 2023-11-22
3.13.0 alpha 3: Wednesday, 2024-01-17
3.13.0 alpha 4: Thursday, 2024-02-15
3.13.0 alpha 5: Tuesday, 2024-03-12
3.13.0 alpha 6: Tuesday, 2024-04-09
3.13.0 beta 1: Wednesday, 2024-05-08 (No new features beyond this point.)
3.13.0 beta 2: Wednesday, 2024-06-05
3.13.0 beta 3: Tuesday, 2024-06-25
3.13.0 beta 4: Tuesday, 2024-07-16
3.13.0 candidate 1: Tuesday, 2024-07-30
3.13.0 candidate 2: Tuesday, 2024-09-03
3.13.0 final: Tuesday, 2024-10-01

Python 3.13.0 will be released 2024-10-1 - If dependency issues are not resolved before release, alternative dependencies will be sought out, or dependencies (and consequently features) will be omitted from the python 3.13 release version of nio-bot until they catch up.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.