Streaming, event-driven access to your Quassel IRC core, built on top of ReactPHP.
This is a lightweight and low-level networking library which can be used to communicate with your Quassel IRC core. It allows you to react to incoming events (such as an incoming message) and to perform new requests (such as sending an outgoing reply message). This can be used to build chatbots, export your channel backlog, list online users, forward backend events as a message to a channel and much more. Unlike conventional IRC chatbots, Quassel IRC allows re-using your existing identity and sharing it with both a person and a chatbot, so that an outside person has no idea about this and only sees a single contact.
- Async execution of requests - Send any number of requests to your Quassel IRC core in parallel (automatic pipeline) and process their responses as soon as results come in.
- Event-driven core - Register your event handler callbacks to react to incoming events, such as an incoming chat message event.
- Lightweight, SOLID design - Provides a thin abstraction that is just good enough and does not get in your way. Future or custom commands and events require little to no changes to be supported.
- Good test coverage - Comes with an automated tests suite and is regularly tested against actual Quassel IRC cores in the wild
Table of contents
We invest a lot of time developing, maintaining and updating our awesome open-source projects. You can help us sustain this high-quality of our work by becoming a sponsor on GitHub. Sponsors get numerous benefits in return, see our sponsoring page for details.
Let's take these projects to the next level together! 🚀
The Quassel IRC protocol is not exactly trivial to explain and has some interesting message semantics. As such, it's highly recommended to check out the examples to get started.
The Factory
is responsible for creating your Client
instance.
$factory = new Clue\React\Quassel\Factory();
This class takes an optional LoopInterface|null $loop
parameter that can be used to
pass the event loop instance to use for this object. You can use a null
value
here in order to use the default loop.
This value SHOULD NOT be given unless you're sure you want to explicitly use a
given event loop instance.
If you need custom connector settings (DNS resolution, TLS parameters, timeouts,
proxy servers etc.), you can explicitly pass a custom instance of the
ConnectorInterface
:
$connector = new React\Socket\Connector(array(
'dns' => '127.0.0.1',
'tcp' => array(
'bindto' => '192.168.10.1:0'
),
'tls' => array(
'verify_peer' => false,
'verify_peer_name' => false
)
));
$factory = new Clue\React\Quassel\Factory(null, $connector);
The createClient($uri)
method can be used to create a new Client
.
It helps with establishing a plain TCP/IP connection to your Quassel IRC core
and probing for the correct protocol to use.
$factory->createClient('localhost')->then(
function (Client $client) {
// client connected (and authenticated)
},
function (Exception $e) {
// an error occured while trying to connect (or authenticate) client
}
);
The $uri
parameter must be a valid URI which must contain a host part and can
optionally be preceded by the quassel://
URI scheme and may contain a port
if your Quassel IRC core is not using the default TCP/IP port 4242
:
$factory->createClient('quassel://localhost:4242');
Quassel supports password-based authentication. If you want to create a "normal"
client connection, you're recommended to pass the authentication details as part
of the URI. You can pass the password h@llo
URL-encoded (percent-encoded) as
part of the URI like this:
$factory->createClient('quassel://user:h%40llo@localhost')->then(
function (Client $client) {
// client sucessfully connected and authenticated
$client->on('data', function ($data) {
// next message to follow would be "SessionInit"
});
}
);
Note that if you do not pass the authentication details as part of the URI, then
this method will resolve with a "bare" Client
right after connecting without
sending any application messages. This can be useful if you need full control
over the message flow, see below for more details.
Quassel uses "heartbeat" messages as a keep-alive mechanism to check the
connection between Quassel core and Quassel client is still active. This project
will automatically respond to each incoming "ping" (heartbeat request) with an
appropriate "pong" (heartbeat response) message. If you do not want this and
want to handle incoming heartbeat request messages yourself, you may pass the
optional ?pong=0
parameter like this:
$factory->createClient('quassel://localhost?pong=0');
This automatic "pong" mechanism allows the Quassel core to detect the connection
to the client is still active. However, it does not allow the client to detect
if the connection to the Quassel core is still active. Because of this, this
project will automatically send a "ping" (heartbeat request) message to the
Quassel core if it did not receive any messages for 60s by default. If no
message has been received after waiting for another period, the connection is
assumed to be dead and will be closed. You can pass the ?ping=120.0
parameter
to change this default interval. The Quassel core uses a configurable ping
interval of 30s by default and also sends all IRC network state changes to the
client, so this mechanism should only really kick in if the connection looks
dead. If you do not want this and want to handle outgoing heartbeat request
messages yourself, you may pass the optional ?ping=0
parameter like this:
$factory->createClient('quassel://localhost?ping=0');
This method uses Quassel IRC's probing mechanism for the correct protocol to use (newer "datastream" protocol or original "legacy" protocol). Protocol handling will be abstracted away for you, so you don't have to worry about this (see also below for more details about protocol messages). Note that this project does not currently implement encryption and compression support.
The Client
is responsible for exchanging messages with your Quassel IRC core
and emitting incoming messages.
It implements the DuplexStreamInterface
,
i.e. it is both a normal readable and writable stream instance.
The Client
exposes several public methods which can be used to send outgoing commands to your Quassel IRC core:
$client->writeClientInit()
$client->writeClientLogin($user, $password);
$client->writeHeartBeatRequest($time);
$client->writeHeartBeatReply($time);
$client->writeBufferRequestBacklog($bufferId, $messageIdFirst, $messageIdLast, $maxAmount, $additional);
$client->writeBufferRequestBacklogAll($messageIdFirst, $messageIdLast, $maxAmount, $additional);
$client->writeBufferInput($bufferInfo, $input);
// many more…
Listing all available commands is out of scope here, please refer to the class outline.
Sending commands is async (non-blocking), so you can actually send multiple commands in parallel. You can send multiple commands in parallel, pending commands will be pipelined automatically.
Quassel IRC has some interesting protocol semantics, which means that commands do not use request-response style. Some commands will trigger a message to be sent in response, see on() for more details.
The on($eventName, $eventHandler)
method can be used to register a new event handler.
Incoming events will be forwarded to registered event handler callbacks:
$client->on('data', function ($data) {
// process an incoming message (raw message object or array)
var_dump($data);
});
$client->on('end', function () {
// connection ended, client will close
});
$client->on('error', function (Exception $e) {
// an error occured, client will close
});
$client->on('close', function () {
// the connection to Quassel IRC just closed
});
The data
event will be forwarded with the PHP representation of whatever the
remote Quassel IRC core sent to this client.
From a consumer perspective this looks very similar to a parsed JSON structure,
but this actually uses a binary wire format under the hood.
This library exposes this parsed structure as-is and does usually not change
anything about it.
There are only few noticable exceptions to this rule:
- Incoming buffers/channels and chat messages use complex data models, so they
are represented by
BufferInfo
andMessage
respectively. All other data types use plain structured data, so you can access its object-based structure very similar to a JSON-like data structure. - The legacy protocol uses plain times for heartbeat messages while the newer
datastream protocol uses
DateTime
objects. This library always converts this toDateTime
for consistency reasons. - The initial
Network
synchronization uses different structures for theIrcUsersAndChannels
format structures depending on which wire-protocol is used. This library always exposes this structure in its simpler "logic" form for consistency reasons. This means it always contains the keysUsers
andChannels
which both contain a list of objects decribing each element.
This combined basically means that you should always get consistent data
events for both the legacy protocol and the newer datastream protocol.
The close()
method can be used to force-close the Quassel connection immediately.
The recommended way to install this library is through Composer. New to Composer?
This will install the latest supported version:
$ composer require clue/quassel-react:^0.7
See also the CHANGELOG for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. It's highly recommended to use PHP 7+ for this project.
Internally, it will use the ext-mbstring
for converting between different
character encodings for message strings.
If this extension is missing, then this library will use a slighty slower Regex
work-around that should otherwise work equally well.
Installing ext-mbstring
is highly recommended.
To run the test suite, you first need to clone this repo and then install all dependencies through Composer:
$ composer install
To run the test suite, go to the project root and run:
$ php vendor/bin/phpunit
The test suite contains both unit tests and functional integration tests. The functional tests require access to a running Quassel core server instance and will be skipped by default.
Note that the functional test suite contains tests that set up your Quassel core (i.e. register your initial user if not already present). This test will be skipped if your core is already set up. You can use a Docker container if you want to test this against a fresh Quassel core:
$ docker run -it --rm -p 4242:4242 clue/quassel-core -d
If you want to run the functional tests, you need to supply your Quassel login details in environment variables like this:
$ QUASSEL_HOST=127.0.0.1 QUASSEL_USER=quassel QUASSEL_PASS=secret phpunit
This project is released under the permissive MIT license.
Did you know that I offer custom development services and issuing invoices for sponsorships of releases and for contributions? Contact me (@clue) for details.
This library took some inspiration from other existing tools and libraries. As such, a huge shoutout to the authors of the following repositories!