A lightweight IRC framework.
Project description
miniirc
Warning
This branch contains a miniirc v2.0.0 pre-release, the documentation is probably out-of-date and breaking changes can and will happen without notice.
A relatively simple thread-safe(-ish) IRC client framework.
To install miniirc, simply run pip3 install miniirc
.
If you have previously used miniirc, you may want to read the deprecations list (last updated 2020-04-28).
This repository is available on both GitHub and GitLab.
Parameters
irc = miniirc.IRC(ip, port, nick, channels=None, *, ssl=None, ident=None, realname=None, persist=True, debug=False, ns_identity=None, auto_connect=True, ircv3_caps=set(), quit_message='I grew sick and died.', ping_interval=60, ping_timeout=None, verify_ssl=True, executor=None)
Note that everything before the * is a positional argument.
Typical usage
You don't need to add every argument, and the ip
, port
, nick
, and
channels
arguments should be specified as positional arguments.
irc = miniirc.IRC('irc.example.com', 6697, 'my-bot', ['#my-channel'], ns_identity=('my-bot', 'hunter2'), executor=concurrent.futures.ThreadPoolExecutor())
If you are not doing anything with the main thread after connecting to IRC,
please call irc.wait_until_disconnected()
to prevent Python from trying to
shut down while miniirc is still connected, breaking thread pools (in
Python 3.9 and later).
irc.wait_until_disconnected()
Parameter descriptions
Parameter | Description |
---|---|
ip |
The IP/hostname of the IRC server to connect to. |
port |
The port to connect to. |
nick |
The nickname of the bot. |
channels |
The channels to join on connect. This can be an iterable containing strings (list, set, etc), or (since v1.5.0) a comma-delimited string. |
ssl |
Enable TLS/SSL. If None , TLS is disabled unless the port is 6697 . |
ident |
The ident to use, defaults to nick . |
realname |
The realname to use, defaults to nick as well. |
persist |
Whether to automatically reconnect. |
debug |
Enables debug mode, prints all IRC messages. This can also be a file-like object (with write mode enabled) if you want debug messages to be written into a file instead of being printed to stdout, or a function (for example logging.debug ). |
ns_identity |
The NickServ account to use as a tuple/list of length 2 (('<user>', '<password>') ). For compatibility, this can be a string ('<user> <password>' ). |
auto_connect |
Runs irc.connect() straight away. |
ircv3_caps |
A set() of additional IRCv3 capabilities to request. SASL is auto-added if ns_identity is specified. |
connect_modes |
A mode string (for example '+B' ) of UMODEs to set when connected. |
quit_message |
Sets the default quit message. This can be modified per-quit with irc.disconnect() . |
ping_interval |
If no packets are sent or received for this amount of seconds, miniirc will send a PING , and if no reply is sent, after the ping timeout, miniirc will attempt to reconnect. Set to None to disable. |
ping_timeout |
The ping timeout used alongside the above ping_interval option, if unspecified will default to ping_interval . |
verify_ssl |
Verifies TLS/SSL certificates. Disabling this is not recommended as it opens the IRC connection up to MiTM attacks. If you have trouble with certificate verification, try running pip3 install certifi first. |
executor |
An instance of concurrent.futures.ThreadPoolExecutor to use when running handlers. |
The only mandatory parameters are ip
, port
, and nick
.
Functions
Function | Description |
---|---|
change_parser(parser=...) |
See the message parser section for documentation. |
connect() |
Connects to the IRC server if not already connected. |
ctcp(target, *msg, reply=False, tags=None) |
Sends a CTCP request or reply to target . |
debug(...) |
Debug, calls print(...) if debug mode is on. |
disconnect(msg=..., *, auto_reconnect=False) |
Disconnects from the IRC server. auto_reconnect will be overridden by self.persist if set to True . |
Handler(...) |
An event handler, see Handlers for more info. |
me(target, *msg, tags=None) |
Sends a /me (CTCP ACTION ) to target . |
msg(target, *msg, tags=None) |
Sends a PRIVMSG to target . target should not contain spaces or start with a colon. |
notice(target, *msg, tags=None) |
Sends a NOTICE to target . target should not contain spaces or start with a colon. |
quote(*msg, force=False, tags=None) |
Sends a raw message to IRC, use force=True to send while disconnected. Do not send multiple commands in one irc.quote() , as the newlines will be stripped and it will be sent as one command. The tags parameter optionally allows you to add a dict with IRCv3 client tags (all starting in + ), and will not be sent to IRC servers that do not support client tags. |
send(*msg, force=False, tags=None) |
Sends a command to the IRC server, treating every positional argument as a parameter. The usage of this is recommended over irc.quote() unless you know what you are doing. |
wait_until_disconnected() |
Waits until the IRC server is disconnected and automatic reconnecting is turned off. |
Note that if force=False
on irc.quote
(or irc.msg
etc is called) while
miniirc is not connected, messages will be temporarily stored and then sent
once miniirc is connected. Setting force=True
will throw errors if miniirc is
completely disconnected (irc.connected
is None
).
irc.quote and irc.send
The two functions irc.quote
and irc.send
may sound similar, however are
fundamentally different: irc.quote()
joins all provided arguments with spaces
and sends them as a raw message to IRC, while irc.send()
treats each argument
as a parameter. If arguments passed to irc.send()
contain spaces, they are
replaced with U+00A0 (a non-breaking space, visually similar to a regular
space however not interpreted as one).
Examples
irc.quote('PRIVMSG', '#channel :Hello,', 'world!')
sends "Hello, world!" to #channel.irc.quote('PRIVMSG', '#channel', 'Hello, world!')
is invalid ("Hello," and "world!" are sent as separate parameters).irc.send('PRIVMSG', '#channel', 'Hello, world!')
will send "Hello, world!" to "#channel".irc.send('PRIVMSG', '#channel :Hello,', 'world!')
will send "world!" to#channel\xa0:Hello,
, where\xa0
is a non-breaking space.
If you are unsure and do not need compatibility with miniirc <1.5.0, use
irc.send()
. PRIVMSG
is just used as an example, if you need to send
PRIVMSG
s use irc.msg()
instead.
Variables
These variables should not be changed outside miniirc.py
.
Variable | Description |
---|---|
active_caps |
A set of IRCv3 capabilities that have been successfully negotiated with the IRC server. This is empty while disconnected. |
connected |
A boolean (or None ), True when miniirc is connected, False when miniirc is connecting, and None when miniirc is not connected. |
current_nick |
New in v1.4.3. The bot/client's current nickname, currently an alias for irc.nick . Do not modify this, and use this instead of irc.nick when getting the bot's current nickname for compatibility with miniirc v2.0.0. |
isupport |
A dict with values (not necessarily strings) from ISUPPORT messages sent to the client. |
msglen |
The maximum length (in bytes) of messages (including \r\n ). This is automatically changed if the server supports the oragono.io/maxline-2 capability. |
nick |
The nickname to use when connecting to IRC. Until miniirc v2.0.0, you should only modify this while disconnected, as it is currently automatically updated with nickname changes. |
The following arguments passed to miniirc.IRC
are also available: ip
,
port
, channels
, ssl
, ident
, realname
, persist
, connect_modes
,
quit_message
, ping_interval
, verify_ssl
.
Handlers
miniirc.Handler
and miniirc.CmdHandler
are function decorators that add
functions to an event handler list. Functions in this list are called when
their respective IRC event(s) are received. Handlers may work on every IRC
object in existence (miniirc.Handler
) or only on specific IRC objects
(irc.Handler
).
The basic syntax for a handler is as followed, where *events
is a list of events (PRIVMSG
, NOTICE
, etc) are called.
import miniirc
@miniirc.Handler(*events)
def handler(irc, hostmask, args):
# irc: An 'IRC' object.
# hostmask: A 'hostmask' object.
# args: A list containing the arguments sent to the command. Everything
# following the first `:` in the command is put into one item
# (args[-1]). If "colon" is "False", the leading ":" (if any)
# is automatically removed. To prevent your code from horribly
# breaking, always set it to False unless you know what you are
# doing.
pass
By default, handlers are run in their own thread. To use the connection thread,
add the thread=False
keyword argument to Handler
.
Recommendations when using handlers:
- Avoid setting the colon parameter to
True
. - Although
Handler
andCmdHandler
currently accept any object that can be converted to a string, every event is converted to a string internally. - Not specifying the
ircv3
parameter when it is not required. - To add handlers to a specific
IRC
object and not every one in existence, useirc.Handler
andirc.CmdHandler
instead. If you want to create aBot
orClient
class and automatically add handlers toIRC
objects created inside it, see making existing functions handlers.
Hostmask object
Hostmasks are tuples with the format ('user', 'ident', 'hostname')
. If ident
and hostname
aren't sent from the server, they will be filled in with the
previous value. If a command is received without a hostmask, all the hostmask
elements will be set to the name of the command. This is deprecated, however,
and when miniirc v2.0.0 is released the hostmask
elements will be set to
empty strings.
Making existing functions handlers
You can make existing functions handlers (for example class instance methods)
with irc.Handler(*events)(handler_function)
. You probably don't want to use
miniirc.Handler
for class instance methods, as this will create a handler
that gets triggered for every IRC
object.
You can also add multiple handlers of the same type easily:
add_handler = irc.Handler('PRIVMSG', colon=False)
add_handler(handler_1)
add_handler(self.instance_handler)
This is useful if you want to create a Bot
(or Client
) class and add
class-specific handlers without creating global process-wide handlers or
creating a wrapper function for every class instance.
IRCv3 support
IRCv3 tags
If you want your handler to support IRCv3 message tags, you need to add
ircv3=True
to the Handler
or CmdHandler
decorator. You will need to add a
tags
parameter to your function after hostmask
. IRCv3 tags are sent to the
handlers as dict
s, with values of either strings or True
.
miniirc will automatically un-escape IRCv3 tag values.
import miniirc
@miniirc.Handler(*events, ircv3=True)
def handler(irc, hostmask, tags, args):
pass
IRCv3 capabilities
You can handle IRCv3 capabilities before connecting using a handler.
You must use force=True
on any irc.quote()
called here, as when this is
called, miniirc may not be fully connected yet.
import miniirc
@miniirc.Handler('IRCv3 my-cap-name')
def handler(irc, hostmask, args):
# Process the capability here
# IRCv3.2 capabilities:
# args = ['my-cap-name', 'IRCv3.2-parameters']
# IRCv3.1 capabilities:
# args = ['my-cap-name']
# Remove the capability from the processing list.
irc.finish_negotiation(args[0]) # This can also be 'my-cap-name'.
Custom message parsers (not recommended)
Documentation on custom message parsers
If the IRC server you are connecting to supports a non-standard message syntax,
you can create custom message parsers. These are called with the raw message
(as a string) and can either return None
to ignore the message or an instance
of IRCMessage
.
Message parser example
This message parser makes the normal parser allow ~
as an IRCv3 tag prefix character.
import miniirc
def my_message_parser(raw_msg):
if msg.startswith('~'):
msg = '@' + msg[1:]
elif msg == 'test':
return miniirc.IRCMessage('PRIVMSG', ('nick', 'user', 'host'), {},
['#channel', 'This is a testing message!'])
return miniirc.ircv3_message_parser(msg)
Changing message parsers
To change message parsers, you can use irc.change_parser(func=...)
. If func
is not
specified, it will default to the built-in parser. You can only change message parsers
on-the-fly (for example in an IRCv3 CAP handler). If you need to change message parsers
before connecting, you can disable auto_connect
and change it then.
irc = miniirc.IRC(..., auto_connect=False)
irc.change_parser(my_message_parser)
irc.connect()
Handling multiple events
If you want to handle multiple events and/or be able to get the name of the
event being triggered, you can use irc.CmdHandler
. This will pass an extra
command
argument to the handler function (between irc
and hostmask
),
containing a string with the command name (such as PRIVMSG
).
Catch-all handlers
Please do not use these unless there is no other alternative.
If you want to handle every event, you can use catch-all handlers. To create
these, you can call irc.CmdHandler()
without any parameters. Note that this
handler will be called many times while connecting (and once connected).
You cannot call irc.Handler()
without parameters.
Example
import miniirc
assert miniirc.ver >= (2,0,0)
@miniirc.Handler('PRIVMSG', 'NOTICE')
def handler(irc, hostmask, args):
print(hostmask[0], 'sent a message to', args[0], 'with content', args[1])
# nickname sent a message to #channel with content Hello, world!
@miniirc.CmdHandler('PRIVMSG', 'NOTICE')
def cmdhandler(irc, command, hostmask, args):
print(hostmask[0], 'sent a', command, 'to', args[0], 'with content',
args[1])
# nickname sent a PRIVMSG to #channel with content Hello, world!
This will print a line whenever the bot gets a PRIVMSG
or NOTICE
.
Misc functions
miniirc provides the following helper functions:
Name | Description |
---|---|
miniirc.get_ca_certs() |
Runs certifi.where() if certifi is installed, otherwise returns None . |
miniirc.ircv3_message_parser(msg) |
The default IRCv2/IRCv3 message parser, returns cmd, hostmask, tags, args . |
miniirc.ver |
A tuple containing version information. |
miniirc.version |
The CTCP VERSION reply, can be changed. |
The version numbering system should be similar to SemVer, however backwards compatibility is preserved where possible when major releases change.
Python version support
- Python 3.3 and below are unsupported and do not work with miniirc.
- Python 3.4, 3.5, and 3.6 are currently supported, but support will likely be dropped in miniirc v2.1.0. Major bugfixes may be backported to v2.0 for a few months after v2.1's release.
- Python 3.7 and above should work with the latest stable version of miniirc.
If there is a bug/error in Python 3.4 or newer, please open an issue or pull request on GitHub or GitLab.
If you are using Python 3.7 or an older version of Python, I strongly recommend updating. Later versions of Python include features such as f-strings that make software development easier.
miniirc_matrix and miniirc_discord
The miniirc_matrix and miniirc_discord libraries allow your existing bot to connect to Matrix and Discord with minimal code changes. Note that these libraries only support a subset of what IRC does and are limited. See the "supported commands" section of their documentation for more information.
miniirc_extras
If you want more advanced(-ish) features such as user tracking, you can use miniirc_extras (GitHub, GitLab). Note that miniirc_extras is still in beta and there will be breaking API changes in the future.
Deprecations
- The
colon
keyword argument toHandler
is deprecated and should be avoided unless you need compatibility with miniirc 1. - As stated in the Python version support section, Python 3.4 support will be dropped in miniirc v2.1.0, however bugfixes will be backported for a few months.
Migrating to miniirc 2
miniirc 2 was released on (TODO). Major breaking changes are listed below, with ones more likely to break code listed first.
The colon keyword argument
The colon
keyword argument to Handler
and CmdHandler
now defaults to
False
instead of True
.
This means that the last argument passed to handlers (args[-1]
) will no
longer start with a colon if it previously did.
Example
While the following code would work as expected in miniirc v1, in miniirc v2 it would ignore the first character of chat messages.
# Warning: This code does not work properly in miniirc v2!
@irc.Handler('PRIVMSG')
def handle_privmsg(irc, hostmask, args):
# args in miniirc v1: ['#channel', ':Message']
# args in miniirc v2: ['#channel', 'Message']
message = args[-1][1:]
# In miniirc v1: message == 'Message'
# In miniirc v2: message == 'essage'
if message.lower().startswith('.help'):
irc.notice(args[0], 'I am a testing bot.')
To fix this, remove the code that ignores ([1:]
) as shown below. If you want
your code to continue to work on miniirc v1, you can add colon=False
to the
handler definition.
@irc.Handler('PRIVMSG', colon=False)
def handle_privmsg(irc, hostmask, args):
# args in miniirc v1: ['#channel', 'Message']
# args in miniirc v2: ['#channel', 'Message']
message = args[-1]
# In miniirc v1: message == 'Message'
# In miniirc v2: message == 'Message'
if message.lower().startswith('.help'):
irc.notice(args[0], 'I am a testing bot.')
irc.current_nick
The irc.nick
value is now the nickname used when connecting to IRC rather
than the current nickname the bot/client has. Use irc.current_nick
for the
current nickname. This was done to fix a bug where miniirc would continue to
append _
to nicknames if they were unavailable.
extended-join
capability
The extended-join
capability is now requested by default. In JOIN
handlers,
args[0]
should be used instead of args[-1]
to get the channel as
extended-join
adds extra arguments to JOIN
messages.
Minor breaking changes that likely won't impact most people
- Unspecified hostmasks will be an empty string instead of the command. Don't rely on this "feature" if possible, simply ignore the hostmask if you do not need it.
irc.ns_identity
will be stored as a tuple instead of a string, for example('username', 'password with spaces')
instead of'username password with spaces'
. Both formats are still accepted in thens_identity
keyword argument.- No exceptions will be raised in
irc.quote
/irc.send
withforce=True
when the socket is closed. Instead of relying on these exceptions, useirc.connected
which is set toNone
when completely disconnected. - The
tags
argument passed to handlers is now read-only. - Internal-only attributes
irc.handlers
,irc.sock
, andirc.sendq
(please do not use these) have been renamed.
Working examples/implementations
Here is a list of some (open-source) bots using miniirc, in alphabetical order:
- irc-rss-feed-bot - Posts RSS entry titles and shortened URLs to IRC channels. Python 3.8+
- irc-url-title-bot - Gets webpage titles from URLs posted in IRC channels. Python 3.8+
- lurklite - A generic configurable IRC, Matrix, and Discord bot. GitHub link.
- stdinbot - A very simple bot that dumps stdin to an IRC channel. GitHub link.
Want to add your own bot/client to this list? Open an issue on GitHub or GitLab.
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
Hashes for miniirc-2.0.0a7-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 64ad958dad2e8f312aa4f88d2d66357a0bf2f50d2d187d70b826c45e8bfc645c |
|
MD5 | e4bc2c9d06564c64ec7fa0947cd6864a |
|
BLAKE2b-256 | d6d6c19af3a21d662c1fc00e66b05390ded63187d3b50d403613880e6c4af6e5 |