asyncio-based rfc2812-compliant IRC Client
Project description
bottom 3.0.0
bottom is a small no-dependency async library for running simple or complex IRC clients
and requires python 3.12+
It's easy to get started with built-in support for common commands, and extensible enough to support any capabilities, including custom encryption, local events, bridging, replication, custom- and multi- syntax compatibility, and more.
Installation
pip install bottom
Documentation
The user guide and API reference are available here including examples for regex based routing of privmsg, custom encryption, and a full list of rfc2812 commands that are supported by default.
Quick Start
The following example creates a client that will:
- connect, identify itself, wait for MOTD, and join a channel
- respond to
PINGautomatically - respond to any
PRIVMSGsent directly to it, or in a channel
import asyncio
import bottom
host = "irc.libera.chat"
port = 6697
ssl = True
NICK = "bottom-bot"
CHANNEL = "#bottom-dev"
bot = bottom.Client(host=host, port=port, ssl=ssl)
@bot.on('CLIENT_CONNECT')
async def connect(**kwargs):
await bot.send('nick', nick=NICK)
await bot.send('user', user=NICK,
realname='https://github.com/numberoverzero/bottom')
# Don't try to join channels until we're past the MOTD
await bottom.wait_for(bot, ["RPL_ENDOFMOTD", "ERR_NOMOTD"])
await bot.send('join', channel=CHANNEL)
@bot.on('PING')
async def keepalive(message: str, **kwargs):
await bot.send('pong', message=message)
@bot.on('PRIVMSG')
async def message(nick: str, target: str, message: str, **kwargs):
if nick == NICK:
return # bot sent this message, ignore
if target == NICK:
target = nick # direct message, respond directly
# else: respond in channel
await bot.send("privmsg", target=target, message=f"echo: {message}")
async def main():
await bot.connect()
try:
# serve until the connection drops...
await bot.wait("client_disconnect")
print("\ndisconnected by remote")
except asyncio.CancelledError:
# ...or we hit ctrl+c
await bot.disconnect()
print("\ndisconnected after ctrl+c")
if __name__ == "__main__":
asyncio.run(main())
API
The public API that you'll typically interact with is small: the Client class and possibly register_pattern.
It is built around sending commands with send(cmd, **kw) (or send_message(msg) for raw IRC lines) and processing
events with @on(event) and wait(event).
If you need to customize serialization, message handling, or signal processing, those are all available with examples in the Extensions documentation.
class Client:
# true when the underlying connection is closed or closing
is_closing() -> bool:
# connects to the given host, port, and optionally over ssl.
async connect() -> None
# start disconnecting if connected. safe to call multiple times.
async disconnect() -> None
# send a known rfc2812 command, formatting kwargs for you
async send(command: str, **kwargs) -> None
# decorate a function (sync or async) to handle an event.
# these can be rfc2812 events (privmsg, ping, notice) or built-in
# events (client_connect, client_disconnect) or your own signals
@on(event: str)(async handler)
# manually trigger an event to be processed by any registered handlers
# for example, to simulate receiving a message:
# my_client.trigger("privmsg", nick=...)
# or send a local-only message to another part of your system:
# trigger("backup-local", backend="s3", session=...)
trigger(event: str, **kwargs) -> asyncio.Task
# wait for an event to be triggered.
async wait(event: str) -> dict
# send raw IRC line. bypasses rfc2812 parsing and validation,
# so you can support custom IRC messages or extensions, like SASL.
async send_message(message: str) -> None
# functions that handle the inbound raw IRC lines.
# by default, Client includes an rfc2812 handler that triggers
# events caught by @Client.on
message_handlers: list[ClientMessageHandler]
# register a new pattern for outbound serialization eg.
# register_pattern("MYCMD", "MYCMD {nick} {target}")
# client.send("MYCMD", nick="n0", target="remote.net")
def register_pattern(command: str, template: str)
# wait for the client to emit one or more events. when mode is "first"
# this returns the events that finished first (more than one event can be triggered
# in a single loop step) and cancels the rest. when mode is "all" this waits
# for all events to trigger.
async def wait_for(client, events: list[str], mode: "first"|"all") -> list[dict]
# helper classes to customize [de]serializing `dict <--> IRC line`
class CommandSerializer:
# register a new serialization pattern. these are sorted and looked up
# when trying to match input params against a valid command format.
# (some commands have multiple formats, like "TOPIC")
def register(command: str, template: str) -> SerializedTemplate
# format a dict of params into the best match template for a given command.
# searches all registered templates for that command from most args -> least args
# until a match is found and applied.
def serialize(command: str, params: dict) -> str
class SerializedTemplate:
# like string.format() but less overhead. applies custom formatters.
def format(params: dict) -> str
# returns an optimized version of string.format() that can use custom formatters
# eg. "{foo:myfunc} said {bar:join_commas}"
@classmethod
def parse(template: str, formatters: dict[str, Callable]) -> SerializedTemplate
# type hints for message handlers
type NextMessageHandler[T: Client] = Callable[[bytes], T, Coroutine[Any, Any, Any]]
type ClientMessageHandler[T: Client] = Callable[[NextMessageHandler[T], T, bytes], Coroutine[Any, Any, Any]]
Contributors
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bottom-3.0.0.tar.gz.
File metadata
- Download URL: bottom-3.0.0.tar.gz
- Upload date:
- Size: 26.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04b87ea92ef1e213d0c8f2af22e46048d1e8cc5bf08e2a19924e94de0610fc34
|
|
| MD5 |
377220ba0e27b8d15e11f59a4f6b454d
|
|
| BLAKE2b-256 |
7027bae725dfeaa4e510a544eff2eaefc518dd655b8bfba60cde8cfffef0a152
|
File details
Details for the file bottom-3.0.0-py3-none-any.whl.
File metadata
- Download URL: bottom-3.0.0-py3-none-any.whl
- Upload date:
- Size: 25.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f3a3733f14f248ff1a3b92d3888cbf6b862e0348a1feffd8ee0c6d601d53d47
|
|
| MD5 |
ae7256fb39385df38b1470e5ad7165ac
|
|
| BLAKE2b-256 |
51988f7dff56ea38e02f787e2ce2b6dac0a413aa7cf16906407568a7b6666ab2
|