aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig18
-rw-r--r--.gitignore15
-rw-r--r--.gitlab-ci.yml23
-rw-r--r--.travis.yml12
-rw-r--r--CHANGELOG.md9
-rw-r--r--Changes36
-rw-r--r--LICENSE202
-rw-r--r--META6.json50
-rw-r--r--README.md71
-rw-r--r--README.rakudoc23
-rw-r--r--bin/test72
-rw-r--r--docs/01-basics.md148
-rw-r--r--docs/02-event-reference.md385
-rw-r--r--docs/03-method-reference.md758
-rw-r--r--docs/04-big-picture-behaviour.md44
-rw-r--r--docs/README.md10
-rw-r--r--examples/01-uppercase-bot.p68
-rw-r--r--examples/02-trickster-bot.p620
-rw-r--r--examples/03-github-notifications.p638
-rw-r--r--examples/04-bash-bot.p626
-rw-r--r--examples/05-bash-bot-with-filter.p632
-rw-r--r--examples/06-multi-server.p623
-rw-r--r--examples/07-multi-server-message-forwarder.p637
-rw-r--r--examples/08-numeric-bot.p632
-rw-r--r--examples/README.md14
-rw-r--r--historical-archive/DESIGN/01-main.md961
-rw-r--r--historical-archive/DESIGN/specs-and-references.md17
-rw-r--r--historical-archive/README.md3
-rw-r--r--lib/IRC/Client.pm6431
-rw-r--r--lib/IRC/Client.rakumod332
-rw-r--r--lib/IRC/Client/Core.rakumod155
-rw-r--r--lib/IRC/Client/Grammar.pm626
-rw-r--r--lib/IRC/Client/Grammar/Actions.pm6119
-rw-r--r--lib/IRC/Client/Handler.rakumod174
-rw-r--r--lib/IRC/Client/Message.pm679
-rw-r--r--lib/IRC/Client/Message.rakumod180
-rw-r--r--lib/IRC/Client/Plugin.rakumod28
-rw-r--r--lib/IRC/Client/Server.pm620
-rw-r--r--logotype/logo_32x32.pngbin617 -> 0 bytes
-rw-r--r--t/00-use.t11
-rw-r--r--t/01-parser.t19
-rw-r--r--t/meta.t7
-rw-r--r--t/release/01-basic.t63
-rw-r--r--t/release/02-multi-server.t76
-rw-r--r--t/release/Test/IRC/Server.pm620
-rw-r--r--t/release/servers/01-basic.pl62
46 files changed, 1074 insertions, 3815 deletions
diff --git a/.editorconfig b/.editorconfig
index 3e899fd..74dde7f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,12 +1,16 @@
[*]
-charset = utf-8
-end_of_line = lf
+charset = utf-8
+end_of_line = lf
insert_final_newline = true
-indent_style = space
-indent_size = 4
-trim_trailing_whitespace = true
-max_line_length = 80
+indent_style = tab
+indent_size = 4
[*.json]
indent_style = space
-indent_size = 4
+indent_size = 2
+
+[*.yml]
+indent_style = space
+indent_size = 2
+
+# vim: ft=dosini
diff --git a/.gitignore b/.gitignore
index 3e54b4c..93356c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,14 @@
-lib/.precomp
-t/release/.precomp
+## Perl 6 precompilation files ##
+.precomp
+
+## Editor files ##
+
+# emacs
*~
+
+# vim
+.*.sw?
+
+# comma
+.idea
+*.iml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..d330e03
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,23 @@
+IRC::Client:
+ only:
+ - master
+ image: rakudo-star
+ before_script:
+ - zef install . --deps-only --test-depends --/test
+ script: AUTHOR_TESTING=1 prove -v -e "perl6 -Ilib" t
+ artifacts:
+ name: "IRC-Client"
+ paths:
+ - META6.json
+ - bin
+ - lib
+ - resources
+ - t
+
+test:
+ except:
+ - master
+ image: rakudo-star
+ before_script:
+ - zef install . --deps-only --test-depends --/test
+ script: AUTHOR_TESTING=1 prove -v -e "perl6 -Ilib" t
diff --git a/.travis.yml b/.travis.yml
index 61e19d1..077ddb0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,13 @@
language: perl6
+
perl6:
- - latest
+ - latest
+
+os:
+ - linux
install:
- - rakudobrew build-zef
- - zef --debug install .
+ - rakudobrew build zef
+ - zef install --deps-only .
+
+script: AUTHOR_TESTING=1 prove -v -e "perl6 -Ilib" t/
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..1003abb
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic
+Versioning](http://semver.org/spec/v2.0.0.html).
+
+## [UNRELEASED]
+- Initial release
diff --git a/Changes b/Changes
deleted file mode 100644
index 49caeeb..0000000
--- a/Changes
+++ /dev/null
@@ -1,36 +0,0 @@
-Revision history for IRC::Client
-
-3.006003
- - Improve docs by adding tables of contents
- - Fixed inability to subscribe to numerics due to irc-\d+ not being a valid
- Perl 6 identifier
-
-3.006002 2016-08-08
- - Emit irc-started before any connection is made
-
-3.006001 2016-08-07
- - Add `:alias` feature (#22)
-
-3.005001 2016-08-07
- - Make addressed regex more restrictive (#21)
-
-3.004004 2016-08-03
- - Implement .match method on Privmsg/Notice message objects
-
-3.004003 2016-08-02
- - Make nick grammar looser to match real-world use rather than RFC (#19)
- - Fix missing user/channel info in debug output for PRIVMSG/NOTICE messages
- - Add support for channel passwords (#18)
-
-3.003006 2016-07-31
- - Fix issue with giving multiple values to channels
-
-3.003005 2016-07-30
- - Fix Privmsg message object match in signature regex
- - Fix warnings in output
-
-3.003004 2016-07-30
- - Fix precompilation bug for Terminal::ANSIColor loading (MasterDuke)
-
-3.003003 2016-07-29
- - Completed "rewrite" API redesign
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 032d0a8..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
- The Artistic License 2.0
-
- Copyright (c) 2000-2006, The Perl Foundation.
-
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-Preamble
-
-This license establishes the terms under which a given free software
-Package may be copied, modified, distributed, and/or redistributed.
-The intent is that the Copyright Holder maintains some artistic
-control over the development of that Package while still keeping the
-Package available as open source and free software.
-
-You are always permitted to make arrangements wholly outside of this
-license directly with the Copyright Holder of a given Package. If the
-terms of this license do not permit the full use that you propose to
-make of the Package, you should contact the Copyright Holder and seek
-a different licensing arrangement.
-
-Definitions
-
- "Copyright Holder" means the individual(s) or organization(s)
- named in the copyright notice for the entire Package.
-
- "Contributor" means any party that has contributed code or other
- material to the Package, in accordance with the Copyright Holder's
- procedures.
-
- "You" and "your" means any person who would like to copy,
- distribute, or modify the Package.
-
- "Package" means the collection of files distributed by the
- Copyright Holder, and derivatives of that collection and/or of
- those files. A given Package may consist of either the Standard
- Version, or a Modified Version.
-
- "Distribute" means providing a copy of the Package or making it
- accessible to anyone else, or in the case of a company or
- organization, to others outside of your company or organization.
-
- "Distributor Fee" means any fee that you charge for Distributing
- this Package or providing support for this Package to another
- party. It does not mean licensing fees.
-
- "Standard Version" refers to the Package if it has not been
- modified, or has been modified only in ways explicitly requested
- by the Copyright Holder.
-
- "Modified Version" means the Package, if it has been changed, and
- such changes were not explicitly requested by the Copyright
- Holder.
-
- "Original License" means this Artistic License as Distributed with
- the Standard Version of the Package, in its current version or as
- it may be modified by The Perl Foundation in the future.
-
- "Source" form means the source code, documentation source, and
- configuration files for the Package.
-
- "Compiled" form means the compiled bytecode, object code, binary,
- or any other form resulting from mechanical transformation or
- translation of the Source form.
-
-
-Permission for Use and Modification Without Distribution
-
-(1) You are permitted to use the Standard Version and create and use
-Modified Versions for any purpose without restriction, provided that
-you do not Distribute the Modified Version.
-
-
-Permissions for Redistribution of the Standard Version
-
-(2) You may Distribute verbatim copies of the Source form of the
-Standard Version of this Package in any medium without restriction,
-either gratis or for a Distributor Fee, provided that you duplicate
-all of the original copyright notices and associated disclaimers. At
-your discretion, such verbatim copies may or may not include a
-Compiled form of the Package.
-
-(3) You may apply any bug fixes, portability changes, and other
-modifications made available from the Copyright Holder. The resulting
-Package will still be considered the Standard Version, and as such
-will be subject to the Original License.
-
-
-Distribution of Modified Versions of the Package as Source
-
-(4) You may Distribute your Modified Version as Source (either gratis
-or for a Distributor Fee, and with or without a Compiled form of the
-Modified Version) provided that you clearly document how it differs
-from the Standard Version, including, but not limited to, documenting
-any non-standard features, executables, or modules, and provided that
-you do at least ONE of the following:
-
- (a) make the Modified Version available to the Copyright Holder
- of the Standard Version, under the Original License, so that the
- Copyright Holder may include your modifications in the Standard
- Version.
-
- (b) ensure that installation of your Modified Version does not
- prevent the user installing or running the Standard Version. In
- addition, the Modified Version must bear a name that is different
- from the name of the Standard Version.
-
- (c) allow anyone who receives a copy of the Modified Version to
- make the Source form of the Modified Version available to others
- under
-
- (i) the Original License or
-
- (ii) a license that permits the licensee to freely copy,
- modify and redistribute the Modified Version using the same
- licensing terms that apply to the copy that the licensee
- received, and requires that the Source form of the Modified
- Version, and of any works derived from it, be made freely
- available in that license fees are prohibited but Distributor
- Fees are allowed.
-
-
-Distribution of Compiled Forms of the Standard Version
-or Modified Versions without the Source
-
-(5) You may Distribute Compiled forms of the Standard Version without
-the Source, provided that you include complete instructions on how to
-get the Source of the Standard Version. Such instructions must be
-valid at the time of your distribution. If these instructions, at any
-time while you are carrying out such distribution, become invalid, you
-must provide new instructions on demand or cease further distribution.
-If you provide valid instructions or cease distribution within thirty
-days after you become aware that the instructions are invalid, then
-you do not forfeit any of your rights under this license.
-
-(6) You may Distribute a Modified Version in Compiled form without
-the Source, provided that you comply with Section 4 with respect to
-the Source of the Modified Version.
-
-
-Aggregating or Linking the Package
-
-(7) You may aggregate the Package (either the Standard Version or
-Modified Version) with other packages and Distribute the resulting
-aggregation provided that you do not charge a licensing fee for the
-Package. Distributor Fees are permitted, and licensing fees for other
-components in the aggregation are permitted. The terms of this license
-apply to the use and Distribution of the Standard or Modified Versions
-as included in the aggregation.
-
-(8) You are permitted to link Modified and Standard Versions with
-other works, to embed the Package in a larger work of your own, or to
-build stand-alone binary or bytecode versions of applications that
-include the Package, and Distribute the result without restriction,
-provided the result does not expose a direct interface to the Package.
-
-
-Items That are Not Considered Part of a Modified Version
-
-(9) Works (including, but not limited to, modules and scripts) that
-merely extend or make use of the Package, do not, by themselves, cause
-the Package to be a Modified Version. In addition, such works are not
-considered parts of the Package itself, and are not subject to the
-terms of this license.
-
-
-General Provisions
-
-(10) Any use, modification, and distribution of the Standard or
-Modified Versions is governed by this Artistic License. By using,
-modifying or distributing the Package, you accept this license. Do not
-use, modify, or distribute the Package, if you do not accept this
-license.
-
-(11) If your Modified Version has been derived from a Modified
-Version made by someone other than you, you are nevertheless required
-to ensure that your Modified Version complies with the requirements of
-this license.
-
-(12) This license does not grant you the right to use any trademark,
-service mark, tradename, or logo of the Copyright Holder.
-
-(13) This license includes the non-exclusive, worldwide,
-free-of-charge patent license to make, have made, use, offer to sell,
-sell, import and otherwise transfer the Package with respect to any
-patent claims licensable by the Copyright Holder that are necessarily
-infringed by the Package. If you institute patent litigation
-(including a cross-claim or counterclaim) against any party alleging
-that the Package constitutes direct or contributory patent
-infringement, then this Artistic License to you shall terminate on the
-date that such litigation is filed.
-
-(14) Disclaimer of Warranty:
-THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
-IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
-WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
-NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
-LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
-BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
-DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
diff --git a/META6.json b/META6.json
index 6b6b61b..33b7095 100644
--- a/META6.json
+++ b/META6.json
@@ -1,26 +1,28 @@
{
- "perl" : "6.c",
- "name" : "IRC::Client",
- "license" : "Artistic-2.0",
- "version" : "3.007011",
- "description" : "Extendable Internet Relay Chat client",
- "tags" : [ "Net", "IRC" ],
- "depends" : [
- "IO::Socket::Async::SSL"
- ],
- "test-depends" : [
- "Test",
- "Test::When",
- "Test::Notice"
- ],
- "provides" : {
- "IRC::Client" : "lib/IRC/Client.pm6",
- "IRC::Client::Plugin" : "lib/IRC/Client.pm6",
- "IRC::Client::Message" : "lib/IRC/Client/Message.pm6",
- "IRC::Client::Grammar" : "lib/IRC/Client/Grammar.pm6",
- "IRC::Client::Grammar::Actions" : "lib/IRC/Client/Grammar/Actions.pm6",
- "IRC::Client::Server" : "lib/IRC/Client/Server.pm6"
- },
- "authors" : ["Zoffix Znet"],
- "support" : {"source" : "https://github.com/zoffixznet/perl6-IRC-Client.git"}
+ "api": "0",
+ "auth": "cpan:TYIL",
+ "authors": [
+ "Patrick Spek <~tyil/raku-devel@lists.sr.ht>"
+ ],
+ "depends": [
+ "IRC::Grammar"
+ ],
+ "description": "My own take on the IRC::Client module",
+ "license": "AGPL-3.0",
+ "meta-version": 0,
+ "name": "IRC::Client",
+ "perl": "6.d",
+ "provides": {
+ "IRC::Client": "lib/IRC/Client.rakumod",
+ "IRC::Client::Core": "lib/IRC/Client/Core.rakumod",
+ "IRC::Client::Handler": "lib/IRC/Client/Handler.rakumod",
+ "IRC::Client::Message": "lib/IRC/Client/Message.rakumod",
+ "IRC::Client::Plugin": "lib/IRC/Client/Plugin.rakumod"
+ },
+ "resources": [
+ ],
+ "source-url": "",
+ "tags": [
+ ],
+ "version": "0.0.0"
}
diff --git a/README.md b/README.md
deleted file mode 100644
index 55555f9..0000000
--- a/README.md
+++ /dev/null
@@ -1,71 +0,0 @@
-[![Build Status](https://travis-ci.org/perl6-community-modules/perl6-IRC-Client.svg)](https://travis-ci.org/perl6-community-modules/perl6-IRC-Client)
-
-# NAME
-
-IRC::Client - Extendable Internet Relay Chat client
-
-# SYNOPSIS
-
-```perl6
- use IRC::Client;
- use Pastebin::Shadowcat;
-
- .run with IRC::Client.new:
- :host<irc.freenode.net>
- :channels<#perl6bot #zofbot>
- :debug
- :plugins(
- class { method irc-to-me ($ where /hello/) { 'Hello to you too!'} }
- )
- :filters(
- -> $text where .chars > 200 {
- 'The output is too large to show here. See: '
- ~ Pastebin::Shadowcat.new.paste: $text;
- }
- );
-```
-
-# DESCRIPTION
-
-The module provides the means to create clients to communicate with
-IRC (Internet Relay Chat) servers. Has support for non-blocking responses
-and output post-processing.
-
-# DOCUMENTATION MAP
-
-* [Blog Post](http://perl6.party/post/IRC-Client-Perl-6-Multi-Server-IRC-Module)
-* [Basics Tutorial](docs/01-basics.md)
-* [Event Reference](docs/02-event-reference.md)
-* [Method Reference](docs/03-method-reference.md)
-* [Big-Picture Behaviour](docs/04-big-picture-behaviour.md)
-* [Examples](examples/)
-
----
-
-#### REPOSITORY
-
-Fork this module on GitHub:
-https://github.com/zoffixznet/perl6-IRC-Client
-
-#### BUGS
-
-To report bugs or request features, please use
-https://github.com/zoffixznet/perl6-IRC-Client/issues
-
-#### AUTHOR
-
-Zoffix Znet (http://zoffix.com/)
-
-#### CONTRIBUTORS
-
-[tyil](https://www.tyil.nl/)
-[MasterDuke17](https://github.com/zoffixznet/perl6-IRC-Client/commits?author=MasterDuke17)
-
-#### LICENSE
-
-You can use and distribute this module under the terms of the
-The Artistic License 2.0. See the `LICENSE` file included in this
-distribution for complete details.
-
-The `META6.json` file of this distribution may be distributed and modified
-without restrictions or attribution.
diff --git a/README.rakudoc b/README.rakudoc
new file mode 100644
index 0000000..30192e0
--- /dev/null
+++ b/README.rakudoc
@@ -0,0 +1,23 @@
+=begin pod
+
+=NAME IRC::Client
+=AUTHOR Patrick Spek <~tyil/raku-devel@lists.sr.ht>
+=VERSION 0.0.0
+
+=head1 Description
+
+My own take on the IRC::Client module
+
+=head1 Installation
+
+Install this module through L<zef|https://github.com/ugexe/zef>:
+
+=begin code :lang<sh>
+zef install IRC::Client
+=end code
+
+=head1 License
+
+This module is distributed under the terms of the AGPL-3.0.
+
+=end pod
diff --git a/bin/test b/bin/test
new file mode 100644
index 0000000..421230f
--- /dev/null
+++ b/bin/test
@@ -0,0 +1,72 @@
+#!/usr/bin/env raku
+
+use v6.d;
+
+use IRC::Client;
+use IRC::Client::Plugin;
+use Log;
+use Log::Level;
+use Number::Denominate;
+
+sub MAIN()
+{
+ # Configure Log
+ $Log::instance = (require ::($*ENV<RAKU_LOG_CLASS> // 'Log::Colored')).new;
+ $Log::instance.add-output($*OUT, %*ENV<RAKU_LOG_LEVEL> // Log::Level::Debug);
+
+ # Start the IRC client
+ my $bot = IRC::Client.new(
+ #host => 'chat.freenode.net',
+ #port => 6697,
+ #ssl => True,
+ host => '127.0.0.1',
+ port => 6667,
+ ssl => False,
+ nicks => <tyil_ircbot tyiltest>,
+ channels => <##t #test>,
+ plugins => [
+ class _ is IRC::Client::Plugin {
+ multi method irc-to-me ($ where * eq 'uptime') {
+ denominate now - INIT now;
+ }
+
+ multi method irc-to-me ($event where { $_.nickname eq 'tyil' && $_ eq 'prefix' }) {
+ $event.irc.prefix;
+ }
+
+ multi method irc-to-me ($event where { $_.nickname eq 'tyil' && $_ eq 'chanlist' }) {
+ $event.irc.channels.join(', ')
+ }
+
+ multi method irc-to-me ($event where { $_.nickname eq 'tyil' && $_.words[0] eq 'join'}) {
+ my $channel = $event.words[1];
+
+ $event.reply("Joining $channel");
+ $event.irc.join($channel);
+ }
+
+ multi method irc-to-me ($event where { $_.nickname eq 'tyil' && $_.words[0] eq 'part'}) {
+ my $channel = $event.words[1];
+
+ $event.reply("Parting $channel");
+ $event.irc.part($channel);
+ }
+
+ multi method irc-mentioned ($) {
+ "Don't take my name in your filthy mouth, peasant"
+ }
+
+ multi method http-get(%payload) {
+ dd %payload<body>;
+ %payload<irc>.privmsg('#test', 'hello there');
+ }
+
+ multi method http-post(%payload) {
+ %payload<irc>.privmsg('#test', %payload<body><this>);
+ }
+ },
+ ],
+ );
+
+ $bot.start;
+}
diff --git a/docs/01-basics.md b/docs/01-basics.md
deleted file mode 100644
index 281c745..0000000
--- a/docs/01-basics.md
+++ /dev/null
@@ -1,148 +0,0 @@
-[[back to doc map]](README.md)
-
-# Basics Tutorial
-
-## Table of Contents
-
-- [Blog Tutorial](#blog-tutorial)
-- [Subscribing to Events](#subscribing-to-events)
- - [Event Handler Input](#event-handler-input)
-- [Responding to Events](#responding-to-events)
- - [Example: Echo Bot](#example-echo-bot)
-- [Generating Messages](#generating-messages)
-- [Up Next](#up-next)
-
-----
-
-This tutorial covers basic usage of `IRC::Client`, without going into
-[all of the supported events](02-event-reference.md) or describing
-[all of the methods](03-method-reference.md) or [available Message
-Objects](04-message-objects.md). This should be enough
-if you're just bringing some functionality using IRC as your interface, rather
-than making, say, a full-featured IRC client.
-
-## Blog Tutorial
-
-There exists a blog post describing this bot and showcasing some of the
-more advanced features. You can find it at [*yet to be published*](#).
-
-## Subscribing to Events
-
-All of the functionality is implemented as "plugins", which are passed to
-the `:plugins` attribute. Plugins are just regular classes, altough they can
-do the `IRC::Client::Plugin` role to obtain extra functionality.
-
-To subscribe to one of [the events](02-event-reference.md), simply create a
-method with the event's name in your class. The tutorial will use the
-`irc-to-me` event, which is a convenience event fired when the bot is addressed
-in-channel or someone sends it a notice or a private message.
-
-### Event Handler Input
-
-The event handlers receive one positional argument, which is an object that
-does the `IRC::Client::Message` role. The actual object received depends on the
-event that triggered the handler. For example, the `irc-to-me` can receive
-these message objects:
-
-```perl6
- IRC::Client::Message::Privmsg::Me
- IRC::Client::Message::Privmsg::Channel
- IRC::Client::Message::Notice::Me
- IRC::Client::Message::Notice::Channel
-```
-
-While message objects differ in methods they offer, all of the above do have
-a `.text` attribute and stringify to its value. This means we can add a type
-constraint on it without having to explicitly call it:
-
-```perl6
- method irc-to-me ($e where /'bot command'/) { 'Do things here!'; }
-```
-
-## Responding to Events
-
-Channel messages, private messages, and notices can be replied to. Their
-message objects have a `.reply` method you can call to send a reply to the
-message's sender. However, it's easier to just return a value from your method
-handler, which will automatically call `.reply` on the message object for you.
-
-Returning a value from your event handler signals to the Client Object that
-it handled the event and no other plugins or event handlers should be tried.
-Your plugin can do the `IRC::Client::Plugin` role (automatically exported
-when you `use IRC::Client`), which provides `$.NEXT` attribute. The value
-of that attribute is special and returning it signals the Client Object
-that your event handler did **not** handle the event and other plugins and
-event handlers should be tried.
-
-Here are the things your event handler can return:
-
-* Value of `$.NEXT`: pass the event to the next plugin or event handler than can
-handle it
-* `Nil` (and a select few other items that don't make sense as replies, such as
-`IRC::Client` object): do not reply to the message, but do not pass the event to
-any other event handler; we handled it
-* `Promise`: when the Promise is `.kept`, use its value for the .reply, unless
-it's a `Nil`. **Note:** you cannot return `$.NEXT` here.
-* *Any other value*: mark the event as handled and don't pass it further. The
-returned value will be given to message object's `.reply` method if
-it has one, or ignored if it doesn't. For `irc-to-me` message objects, this
-means the value will be sent back to the sender of the original message
-
-### Example: Echo Bot
-
-In this example, we subscribe to the `irc-to-me` event and respond by returning
-the original message, prefixed with `You said `.
-
-```perl6
- use IRC::Client;
-
- .run with IRC::Client.new:
- :host<irc.freenode.net>
- :channels('#perl6bot', '#zofbot', '#myown' => 's3cret')
- :debug
- :plugins(
- class { method irc-to-me ($e) { "You said $e.text()"} }
- )
-```
-
-## Generating Messages
-
-If your plugin needs to generate messages instead of merely responding to
-commands, you can use the Client Object's `.send` method. Your plugin needs
-to do the `IRC::Client::Plugin` role to get access to the Client Object via
-the `$.irc` attribute:
-
-```perl6
-use IRC::Client;
-
-class AlarmBot does IRC::Client::Plugin {
- method irc-connected ($) {
- react {
- whenever Supply.interval(3) {
- $.irc.send: :where<#perl6> :text<Three seconds passed!>;
- }
- }
- }
-}
-
-.run with IRC::Client.new:
- :nick<MahBot>
- :host<irc.freenode.net>
- :channels<#perl6>
- :debug
- :plugins(AlarmBot.new)
-```
-
-Here, we subscribe to the `irc-connected` event (using an anonymous parameter
-for its message object, since we don't need it). It fires whenever we
-successfully connect to a server. In the event handler we setup a
-`react`/`whenever` loop, with a `Supply` generating an event every three
-seconds. In the `whenever` block, we use the `$.irc` attribute provided
-by the `IRC::Client::Plugin` role to call method `.send` on the Client Object.
-In the `:where` parameter, we specify we want to send the message to
-channel `#perl6` and the `:text` parameter contains the text we want to send.
-The bot will send that text every 3 seconds.
-
-## Up Next
-
-Read [the event reference](02-event-reference.md) next.
diff --git a/docs/02-event-reference.md b/docs/02-event-reference.md
deleted file mode 100644
index fdf9d0a..0000000
--- a/docs/02-event-reference.md
+++ /dev/null
@@ -1,385 +0,0 @@
-[[back to doc map]](README.md)
-
-# Event Reference
-
-## Table of Contents
-
-- [Responding to Events](#responding-to-events)
-- [Event Map](#event-map)
-- [Event Triggers](#event-triggers)
- - [`irc-addressed`](#irc-addressed)
- - [`irc-all`](#irc-all)
- - [`irc-connected`](#irc-connected)
- - [`irc-join`](#irc-join)
- - [`irc-mentioned`](#irc-mentioned)
- - [`irc-mode`](#irc-mode)
- - [`irc-mode-channel`](#irc-mode-channel)
- - [`irc-mode-me`](#irc-mode-me)
- - [`irc-nick`](#irc-nick)
- - [`irc-notice`](#irc-notice)
- - [`irc-notice-channel`](#irc-notice-channel)
- - [`irc-notice-me`](#irc-notice-me)
- - [`irc-numeric`](#irc-numeric)
- - [`irc-part`](#irc-part)
- - [`irc-privmsg`](#irc-privmsg)
- - [`irc-privmsg-channel`](#irc-privmsg-channel)
- - [`irc-privmsg-me`](#irc-privmsg-me)
- - [`irc-quit`](#irc-quit)
- - [`irc-started`](#irc-started)
- - [`irc-to-me`](#irc-to-me)
- - [`irc-unknown`](#irc-unknown)
- - [`irc-nXXX`](#irc-nXXX)
-- [Up Next](#up-next)
-
----
-
-The module offers named, numeric, and convenience events. The named and
-numeric events correspond to IRC protocol events, while convenience events
-are an extra layer provided to make using the module easier. This means one
-IRC event can trigger several events of the module. For example, if someone
-addresses our bot in a channel, the following chain of events will be fired:
-
- irc-addressed ▶ irc-to-me ▶ irc-privmsg-channel ▶ irc-privmsg ▶ irc-all
-
-The events are ordered from "narrowest" to "widest": `irc-addressed` can be
-triggered only in-channel, when our bot is addressed; `irc-to-me` can also
-be triggered via notice and private message, so it's wider;
-`irc-privmsg-channel` includes all channel messages, so it's wider still;
-and `irc-privmsg` also includes private messages to our bot. The chain ends
-by the widest event of them all: `irc-all`.
-
-## Responding to Events
-
-See [section in Basic Tutorial](01-basics.md#responding-to-events) for
-responding by returning a value from the event handler.
-
-The Message Objects received by the event handlers for the `irc-privmsg` and
-`irc-notice` event chains also provide a `.reply` method using which you
-can reply to the event. When this method is called `.is-replied` attribute
-of the Message Object is set to `True`, which signals to the Client Object
-that the returned value from the event handler should be discarded.
-
-## Event Map
-
-In the chart below, `irc-nXXX` stands for numeric events where `XXX` is a
-three-digit number. See [this numerics
-table](https://www.alien.net.au/irc/irc2numerics.html) for meaning of codes,
-depending on the server used.
-
-```
-irc-addressed ▶ irc-to-me ▶ irc-privmsg-channel ▶ irc-privmsg ▶ irc-all
- irc-mentioned ▶ irc-privmsg-channel ▶ irc-privmsg ▶ irc-all
- irc-privmsg-channel ▶ irc-privmsg ▶ irc-all
- irc-to-me ▶ irc-privmsg-me ▶ irc-privmsg ▶ irc-all
-
-irc-addressed ▶ irc-to-me ▶ irc-notice-channel ▶ irc-notice ▶ irc-all
- irc-mentioned ▶ irc-notice-channel ▶ irc-notice ▶ irc-all
- irc-notice-channel ▶ irc-notice ▶ irc-all
- irc-to-me ▶ irc-notice-me ▶ irc-notice ▶ irc-all
-
- irc-mode-channel ▶ irc-mode ▶ irc-all
- irc-mode-me ▶ irc-mode ▶ irc-all
-
- irc-connected ▶ irc-nXXX ▶ irc-numeric ▶ irc-all
- irc-nXXX ▶ irc-numeric ▶ irc-all
- irc-join ▶ irc-all
- irc-nick ▶ irc-all
- irc-part ▶ irc-all
- irc-quit ▶ irc-all
- irc-unknown ▶ irc-all
-
- irc-started
-```
-
-**Note:** `irc-started` is a special event that's exempt from the rules
-applicable to all other events and their event handlers:
-
-* It's called just once per call of `IRC::Client`'s `.run` method, regardless
-of how many times the client reconnects
-* When it's called, there's no guarantee the connections to servers have
-been fully established yet or channels joined yet.
-* Unless all other event handlers, this one does not take any arguments
-* Return values from handlers are ignored and the event is propagated to all of
-the plugins
-* This event does not trigger `irc-all` event
-
-## Event Triggers
-
-### `irc-addressed`
-
-```
-irc-addressed ▶ irc-to-me ▶ irc-privmsg-channel ▶ irc-privmsg ▶ irc-all
-irc-addressed ▶ irc-to-me ▶ irc-notice-channel ▶ irc-notice ▶ irc-all
-```
-
-This event chain is triggered when the client is addressed in a channel either
-via a `PRIVMSG` or `NOTICE` IRC message. 'Addressed' means the message line
-starts with the current nickname of the client or one of its aliases,
-followed by `;` or `,`
-characters, followed by any number of whitespace; or
-in regex terms, matches `/^ [$nick | @aliases] <[,:]> \s* /`.
-This prefix portion will be
-**stripped** from the actual message.
-
-Possible message objects received by event handler:
-
-* `IRC::Client::Message::Privmsg::Channel`
-* `IRC::Client::Message::Notice::Channel`
-
-### `irc-all`
-
-```
-irc-all
-```
-
-Triggered on all events, except for the special `irc-started` event.
-
-Possible message objects received by event handler:
-* `IRC::Client::Message::Notice::Channel`
-* `IRC::Client::Message::Notice::Me`
-
-### `irc-connected`
-
-```
-irc-connected ▶ irc-n001 ▶ irc-numeric ▶ irc-all
-```
-
-Triggered on `001` numeric IRC command that indicates we successfully
-connected to the IRC server and obtained a nickname. *Note:* it's not
-guaranteed that we already joined all the channels when this event is triggered;
-in fact, it's more likely that we haven't yet. *Also note:* that in long
-running programs this event will be triggered more than once, because the
-client automatically reconnects when connection drops, so the event will
-be triggered on each reconnect. See also `irc-started`
-
-Receives `IRC::Client::Message::Numeric` message object.
-
-### `irc-join`
-
-```
-irc-join ▶ irc-all
-```
-
-Triggered when someone joins a channel we are in. *Note:* typically the
-server will generate this event when *we* join a channel too.
-Receives `IRC::Client::Message::Join` message object.
-
-### `irc-mentioned`
-
-```
-irc-mentioned ▶ irc-privmsg-channel ▶ irc-privmsg ▶ irc-all
-irc-mentioned ▶ irc-notice-channel ▶ irc-notice ▶ irc-all
-```
-
-This event chain is triggered when the client is mentioned in a channel either
-via a `PRIVMSG` or `NOTICE` IRC message. Being mentioned means the message
-contains our nick or one of the aliases delimited by word boundaries on both
-sides; or in regex terms, matches `/ << [$nick | @aliases] >> /`.
-
-Possible message objects received by event handler:
-* `IRC::Client::Message::Privmsg::Channel`
-* `IRC::Client::Message::Notice::Channel`
-
-### `irc-mode`
-
-```
-irc-mode ▶ irc-all
-```
-
-Triggered when `MODE` commands are performed on the client or on the channel
-we are in.
-
-Possible message objects received by event handler:
-* `IRC::Client::Message::Mode::Channel`
-* `IRC::Client::Message::Mode::Me`
-
-### `irc-mode-channel`
-
-```
-irc-mode-channel ▶ irc-mode ▶ irc-all
-```
-
-Triggered when `MODE` commands are performed on a channel the client is in
-Receives `IRC::Client::Message::Mode::Channel` message object.
-
-### `irc-mode-me`
-
-```
-irc-mode-me ▶ irc-mode ▶ irc-all
-```
-
-Triggered when `MODE` commands are performed on the client.
-Receives `IRC::Client::Message::Mode::Me` message object.
-
-### `irc-nick`
-
-```
-irc-nick ▶ irc-all
-```
-
-Triggered when someone in a channel we are in changes nick.
-*Note:* typically the server will generate this event when *we* change
-a nick too.
-Receives `IRC::Client::Message::Nick` message object.
-
-### `irc-notice`
-
-```
-irc-notice ▶ irc-all
-```
-
-Triggered on `NOTICE` messages sent to a channel the client is in or to
-the client directly.
-
-Possible message objects received by event handler:
-* `IRC::Client::Message::Notice::Channel`
-* `IRC::Client::Message::Notice::Me`
-
-### `irc-notice-channel`
-
-```
-irc-notice-channel ▶ irc-notice ▶ irc-all
-```
-
-Triggered on `NOTICE` messages sent to a channel the client is in.
-Receives `IRC::Client::Message::Notice::Channel` message object.
-
-### `irc-notice-me`
-
-```
-irc-notice-me ▶ irc-notice ▶ irc-all
-```
-
-Triggered on `NOTICE` messages sent directly to the client.
-Receives `IRC::Client::Message::Notice::Me` message object.
-
-### `irc-numeric`
-
-```
-irc-numeric ▶ irc-nXXX ▶ irc-all
-```
-
-Triggered on numeric IRC commands.
-Receives `IRC::Client::Message::Numeric` message object.
-
-### `irc-part`
-
-```
-irc-part ▶ irc-all
-```
-
-Triggered when someone parts (leaves without quitting IRC entirely) a channel
-we are in. Receives `IRC::Client::Message::Part` message object.
-
-### `irc-privmsg`
-
-```
-irc-privmsg ▶ irc-all
-```
-
-Triggered on `PRIVMSG` messages sent to a channel the client is in or to
-the client directly.
-
-Possible message objects received by event handler:
-* `IRC::Client::Message::Privmsg::Channel`
-* `IRC::Client::Message::Privmsg::Me`
-
-### `irc-privmsg-channel`
-
-```
-irc-privmsg-channel ▶ irc-privmsg ▶ irc-all
-```
-
-Triggered on `PRIVMSG` messages sent to a channel the client is in.
-Receives `IRC::Client::Message::Privmsg::Channel` message object.
-
-
-### `irc-privmsg-me`
-
-```
-irc-privmsg-me ▶ irc-privmsg ▶ irc-all
-```
-
-Triggered on `PRIVMSG` messages sent directly to the client.
-Receives `IRC::Client::Message::Privmsg::Me` message object.
-
-### `irc-quit`
-
-```
-irc-quit ▶ irc-all
-```
-
-Triggered when someone in a channel we are in quits IRC.
-Receives `IRC::Client::Message::Quit` message object.
-
-### `irc-started`
-
-```
-irc-started
-```
-
-The event is different from all others (see end of `Event Map` section). It's
-triggered just once per call of `.run` method on `IRC::Client` object,
-regardless of how many times the client reconnects, and it's
-called on all of the plugins, regardless of the return value of the
-event handler.
-
-Does not receive any arguments.
-
-### `irc-to-me`
-
-```
-irc-addressed ▶ irc-to-me ▶ irc-privmsg-channel ▶ irc-privmsg ▶ irc-all
- irc-to-me ▶ irc-privmsg-me ▶ irc-privmsg ▶ irc-all
-
-irc-addressed ▶ irc-to-me ▶ irc-notice-channel ▶ irc-notice ▶ irc-all
- irc-to-me ▶ irc-notice-me ▶ irc-notice ▶ irc-all
-```
-
-This event chain is triggered when the client is addressed in a channel via
-a `PRIVMSG` or `NOTICE` IRC message or receives a private or notice
-message directly. In cases where the trigger happened due to being addressed
-in channel, the prefix used for addressing (nick|aliases + `,` or `.` +
-whitespace) will be stripped from the message.
-
-Possible message objects received by event handler:
-* `IRC::Client::Message::Privmsg::Channel`
-* `IRC::Client::Message::Privmsg::Me`
-* `IRC::Client::Message::Notice::Channel`
-* `IRC::Client::Message::Notice::Me`
-
-*Note irrelevant to common users:* to avoid spurious triggers during
-IRC server connection negotiation, this event does *not* fire until the server
-deems the client connected; that is, sends the IRC `001` command.
-
-### `irc-unknown`
-
-```
-irc-unknown ▶ irc-all
-```
-
-Triggered when an unknown event is generated. You're not supposed to receive
-any of these and receiving one likely indicates a problem with `IRC::Client`.
-Please report this [on the Issue
-tracker](https://github.com/zoffixznet/perl6-IRC-Client/issues/new),
-indicating what server generated such a message and include your code too,
-if possible.
-
-Receives `IRC::Client::Message::Unknown` message object.
-
-### `irc-nXXX`
-
-**Note:*** `XXX` stands for a three-digit numeric code of the command that
-triggered the event, for example `irc-n001`. See `irc-numeric` for event trigger
-that responds to all numerics.
-
-```
-irc-nXXX ▶ irc-numeric ▶ irc-all
-```
-
-Triggered on numeric IRC commands.
-Receives `IRC::Client::Message::Numeric` message object.
-
-## Up Next
-
-Read [the method reference](03-method-reference.md) next.
diff --git a/docs/03-method-reference.md b/docs/03-method-reference.md
deleted file mode 100644
index 2e19e9e..0000000
--- a/docs/03-method-reference.md
+++ /dev/null
@@ -1,758 +0,0 @@
-[[back to doc map]](README.md)
-
-# Method Reference
-
-This document describes events available on various objects in use when working
-with `IRC::Client`.
-
-## Table of Contents
-
-- [Message Objects (`IRC::Client::Message` and subclasses)](#message-objects-ircclientmessage-and-subclasses)
- - [Message Object Hierarchy](#message-object-hierarchy)
- - [Methods and Attributes](#methods-and-attributes)
- - [`IRC::Client::Message`](#ircclientmessage)
- - [`.irc`](#irc)
- - [`.nick`](#nick)
- - [`.username`](#username)
- - [`.host`](#host)
- - [`.usermask`](#usermask)
- - [`.command`](#command)
- - [`.server`](#server)
- - [`.args`](#args)
- - [`.Str`](#str)
- - [`IRC::Client::Message::Join`](#ircclientmessagejoin)
- - [`.channel`](#channel)
- - [`IRC::Client::Message::Nick`](#ircclientmessagenick)
- - [`.new-nick`](#new-nick)
- - [`IRC::Client::Message::Numeric`](#ircclientmessagenumeric)
- - [`IRC::Client::Message::Part`](#ircclientmessagepart)
- - [`.channel`](#channel-1)
- - [`IRC::Client::Message::Ping`](#ircclientmessageping)
- - [`.reply`](#reply)
- - [`IRC::Client::Message::Quit`](#ircclientmessagequit)
- - [`IRC::Client::Message::Unknown`](#ircclientmessageunknown)
- - [`.Str`](#str-1)
- - [`IRC::Client::Message::Mode`](#ircclientmessagemode)
- - [`.modes`](#modes)
- - [`IRC::Client::Message::Mode::Channel`](#ircclientmessagemodechannel)
- - [`.channel`](#channel-2)
- - [`IRC::Client::Message::Mode::Me`](#ircclientmessagemodeme)
- - [`IRC::Client::Message::Notice`](#ircclientmessagenotice)
- - [`.text`](#text)
- - [`.replied`](#replied)
- - [`.Str`](#str-2)
- - [`IRC::Client::Message::Notice::Channel`](#ircclientmessagenoticechannel)
- - [`.channel`](#channel-3)
- - [`.reply`](#reply-1)
- - [`IRC::Client::Message::Notice::Me`](#ircclientmessagenoticeme)
- - [`.reply`](#reply-2)
- - [`IRC::Client::Message::Privmsg`](#ircclientmessageprivmsg)
- - [`.text`](#text-1)
- - [`.replied`](#replied-1)
- - [`.Str`](#str-3)
- - [`IRC::Client::Message::Privmsg::Channel`](#ircclientmessageprivmsgchannel)
- - [`.channel`](#channel-4)
- - [`.reply`](#reply-3)
- - [`IRC::Client::Message::Privmsg::Me`](#ircclientmessageprivmsgme)
- - [`.reply`](#reply-4)
-- [Server Object (`IRC::Client::Server`)](#server-object-ircclientserver)
- - [Labels](#labels)
- - [Methods and Attributes](#methods-and-attributes-1)
- - [`.label`](#label)
- - [`.channels`](#channels)
- - [`.nick`](#nick-1)
- - [`.alias`](#alias)
- - [`.host`](#host-1)
- - [`.port`](#port)
- - [`.password`](#password)
- - [`.username`](#username-1)
- - [`.userhost`](#userhost)
- - [`.userreal`](#userreal)
- - [`.Str`](#str-4)
- - [Writable Non-Writable Attributes](#writable-non-writable-attributes)
- - [`.current-nick`](#current-nick)
- - [`.is-connected`](#is-connected)
- - [`.has-quit`](#has-quit)
- - [`.has-quit`](#has-quit-1)
-- [Client Object (`IRC::Client`)](#client-object-ircclient)
- - [Methods and Attributes](#methods-and-attributes-2)
- - [`.join`](#join)
- - [`.new`](#new)
- - [`:channels`](#channels-1)
- - [`:debug`](#debug)
- - [`:filters`](#filters)
- - [`:host`](#host-2)
- - [`:nick`](#nick-2)
- - [`:alias`](#alias-1)
- - [`:password`](#password-1)
- - [`:plugins`](#plugins)
- - [`:port`](#port-1)
- - [`:servers`](#servers)
- - [`:username`](#username-2)
- - [`:userhost`](#userhost-1)
- - [`:userreal`](#userreal-1)
- - [`.nick`](#nick-3)
- - [`.part`](#part)
- - [`.quit`](#quit)
- - [`.run`](#run)
- - [`.send`](#send)
-- [Up Next](#up-next)
-
----
-
-## Message Objects (`IRC::Client::Message` and subclasses)
-
-All event handlers (except for special `irc-started`) receive one positional
-argument that does `IRC::Client::Message` role and is refered to as
-the Message Object throughout the documentation. The actual received
-message object depends on the event the event handler is subscribed to.
-See [event reference](02-event-reference.md) to learn which message objects
-an event can receive.
-
-### Message Object Hierarchy
-
-All message objects reside in the `IRC::Client::Message` package and
-follow the following hierarchy, with children having all the methods
-and attributes of their parents.
-
-```
-IRC::Client::Message
-│
-├───IRC::Client::Message::Join
-├───IRC::Client::Message::Nick
-├───IRC::Client::Message::Numeric
-├───IRC::Client::Message::Part
-├───IRC::Client::Message::Ping
-├───IRC::Client::Message::Quit
-├───IRC::Client::Message::Unknown
-│
-├───IRC::Client::Message::Mode
-│  ├───IRC::Client::Message::Mode::Channel
-│  └───IRC::Client::Message::Mode::Me
-│
-├───IRC::Client::Message::Notice
-│  ├───IRC::Client::Message::Notice::Channel
-│  └───IRC::Client::Message::Notice::Me
-│
-└───IRC::Client::Message::Privmsg
-   ├───IRC::Client::Message::Privmsg::Channel
-  └───IRC::Client::Message::Privmsg::Me
-```
-
-### Methods and Attributes
-
-Subclasses inherit all the methods and attributes of their parents (see
-hierarchy chart above). Some event handlers can receive more than one
-type of a message object. In many cases, the type can be differentiated
-with a safe-method-call operator (`.?`):
-
-```perl6
- method irc-privmsg ($e) {
- if $e.?channel {
- say '$e is a IRC::Client::Message::Privmsg::Channel';
- }
- else {
- say '$e is a IRC::Client::Message::Privmsg::Me';
- }
- }
-```
-
----
-
-#### `IRC::Client::Message`
-
-Object is never sent to event handlers and merely provides commonality to
-its subclasses.
-
-##### `.irc`
-
-Contains the `IRC::Client` object.
-
-##### `.nick`
-
-Contains the nick of the sender of the message.
-
-##### `.username`
-
-Contains the username of the sender of the message.
-
-##### `.host`
-
-Contains the host of the sender of the message.
-
-##### `.usermask`
-
-Contains the usermask of the sender of the message. That is
-string constructed as `nick!username@host`
-
-##### `.command`
-
-The IRC command responsible for this event, such as `PRIVMSG` or `001`.
-
-##### `.server`
-
-The `IRC::Client::Server` object from which the event originates.
-
-##### `.args`
-
-A possibly-empty list of arguments, received for the IRC command that triggered
-the event.
-
-##### `.Str`
-
-(affects stringification of message objects). Returns a string
-constructed from `":$!usermask $!command $!args[]"`, but is overriden to
-a different value by some message objects.
-
----
-
-#### `IRC::Client::Message::Join`
-
-##### `.channel`
-
-Contains the channel name of the channel that was joined
-
----
-
-#### `IRC::Client::Message::Nick`
-
-##### `.new-nick`
-
-Contains the new nick switched to (`.nick` attribute contains the old one).
-
----
-
-#### `IRC::Client::Message::Numeric`
-
-Does not offer any object-specific methods. Use the `.command` attribute
-to find out the actual 3-digit IRC command that triggered the event.
-
----
-
-#### `IRC::Client::Message::Part`
-
-##### `.channel`
-
-Contains the channel name of the channel that was parted. Use `.args`
-attribute to get any potential parting messages.
-
----
-
-#### `IRC::Client::Message::Ping`
-
-**Included in the docs for completeness only.** Used internally. Not sent
-to any event handlers and `irc-ping` is not a valid event.
-
-##### `.reply`
-
-Takes no arguments. Replies to the server with appropriate `PONG` IRC command.
-
----
-
-#### `IRC::Client::Message::Quit`
-
-Does not offer any object-specific methods. Use `.args`
-attribute to get any potential quit messages.
-
----
-
-#### `IRC::Client::Message::Unknown`
-
-##### `.Str`
-
-Overrides the default stringification string to
-`"❚⚠❚ :$.usermask $.command $.args[]"`
-
----
-
-#### `IRC::Client::Message::Mode`
-
-Object is never sent to event handlers and merely provides commonality to
-its subclasses.
-
-##### `.modes`
-
-Contains the modes set by the IRC command that triggered the event. When
-modes are set on the channel, contains a list of `Pair`s where the key
-is the sign of the mode (`+` or `-`) and the value if the mode letter itself.
-When modes are set on the client, contains just a list of modes as strings,
-without any signs.
-
----
-
-#### `IRC::Client::Message::Mode::Channel`
-
-##### `.channel`
-
-Contains the channel on which the modes were set.
-
----
-
-#### `IRC::Client::Message::Mode::Me`
-
-Does not offer any object-specific methods.
-
----
-
-#### `IRC::Client::Message::Notice`
-
-Object is never sent to event handlers and merely provides commonality to
-its subclasses.
-
-##### `.text`
-
-Writable attribute. Contains the text of the message. For addressed commands,
-the nick will be stripped. Use `.args` method to get full, original text,
-which will be the second value in the list.
-
-##### `.replied`
-
-Writable `Bool` attribute. Automatically gets set to `True` by the
-`.reply` method. If set to `True`, indicates to the Client Object that
-the event handler's value must not be used as a reply to the message.
-
-##### `.Str`
-
-Overrides stringification of the message object to be the value of the
-`.text` attribute.
-
----
-
-#### `IRC::Client::Message::Notice::Channel`
-
-##### `.channel`
-
-Contains the channel to which the message was sent.
-
-##### `.reply`
-
-```perl6
- $e.reply: "Hello, World!";
- $e.reply: "Hello, World!", :where<#perl6>;
- $e.reply: "Hello, World!", :where<Zoffix>;
-```
-
-Replies to the sender of the message using the `NOTICE` IRC command. The
-optional `:where` argument specifies a channel or nick
-where to send the message and defaults to the channel in which the message
-originated.
-
----
-
-#### `IRC::Client::Message::Notice::Me`
-
-##### `.reply`
-
-```perl6
- $e.reply: "Hello, World!";
- $e.reply: "Hello, World!", :where<Zoffix>;
- $e.reply: "Hello, World!", :where<#perl6>;
-```
-
-Replies to the sender of the message using the `NOTICE` IRC command. The
-optional `:where` argument specifies a nick or channel
-where to send the message and defaults to the nick from which the message
-originated.
-
----
-
-#### `IRC::Client::Message::Privmsg`
-
-Object is never sent to event handlers and merely provides commonality to
-its subclasses.
-
-##### `.text`
-
-Writable attribute. Contains the text of the message. For addressed commands,
-the nick will be stripped. Use `.args` method to get full, original text,
-which will be the second value in the list.
-
-##### `.replied`
-
-Writable `Bool` attribute. Automatically gets set to `True` by the
-`.reply` method. If set to `True`, indicates to the Client Object that
-the event handler's value must not be used as a reply to the message.
-
-##### `.Str`
-
-Overrides stringification of the message object to be the value of the
-`.text` attribute.
-
----
-
-#### `IRC::Client::Message::Privmsg::Channel`
-
-##### `.channel`
-
-Contains the channel to which the message was sent.
-
-##### `.reply`
-
-```perl6
- $e.reply: "Hello, World!";
- $e.reply: "Hello, World!", :where<#perl6>;
- $e.reply: "Hello, World!", :where<Zoffix>;
-```
-
-Replies to the sender of the message using the `PRIVMSG` IRC command. The
-optional `:where` argument specifies a channel or nick
-where to send the message and defaults to the channel in which the message
-originated.
-
----
-
-#### `IRC::Client::Message::Privmsg::Me`
-
-##### `.reply`
-
-```perl6
- $e.reply: "Hello, World!";
- $e.reply: "Hello, World!", :where<Zoffix>;
- $e.reply: "Hello, World!", :where<#perl6>;
-```
-
-Replies to the sender of the message using the `PRIVMSG` IRC command. The
-optional `:where` argument specifies a nick or channel
-where to send the message and defaults to the nick from which the message
-originated.
-
----
-
-## Server Object (`IRC::Client::Server`)
-
-The Server Object represents one of the IRC servers the client is connected
-to. It's returned by the `.server` attribute of the Message Object and
-can be passed to various Client Object methods to indicate to which server
-a command should be sent to.
-
-Client Object's `%.servers` attribute contains all of the `IRC::Client::Server`
-objects with their keys as labels and values as objects themselves.
-
-### Labels
-
-Each `IRC::Client::Server` object has a label that was given to it during
-the creation of `IRC::Client` object. `IRC::Client::Server` objects stringify
-to the value of their labels and methods that can take an
-`IRC::Client::Server` object can also take a `Str` with the label of that server
-instead.
-
-### Methods and Attributes
-
-#### `.label`
-
-The label of this server.
-
-#### `.channels`
-
-A list of `Str` or `Pair` containing all the channels the client is in on this
-server. Pairs represent channels with channel passwords, where the key is
-the channel and the value is its password.
-
-#### `.nick`
-
-A list of nicks the client uses on this server. If one nick is
-taken, next one in the list will be attempted to be used.
-
-#### `.alias`
-
-A list of aliases on this server.
-
-#### `.host`
-
-The host of the server.
-
-#### `.port`
-
-The port of the server.
-
-#### `.password`
-
-The password of the server.
-
-#### `.username`
-
-Our username on this server.
-
-#### `.userhost`
-
-Our hostname on this server.
-
-#### `.userreal`
-
-The "real name" of our client.
-
-#### `.Str`
-
-Affects stringification of the object and makes it stringify to the value
-of `.label` attribute.
-
-#### Writable Non-Writable Attributes
-
-The following attributes are writable, however, they are written to by
-the internals and the behaviour when writing to them directly is undefined.
-
-##### `.current-nick`
-
-Writable attribute. Our currently-used nick. Will be one of the nicks returned
-by `.nicks`.
-
-##### `.is-connected`
-
-Writable `Bool` attribute. Indicates whether we're currently in a state
-where the server considers us connected. This defaults to `False`, then is set
-to `True` when the server sends us `001` IRC command and set back to `False`
-when the socket connection breaks.
-
-##### `.has-quit`
-
-Writable `Bool` attribute. Set to `True` when `.quit` method is called
-on the Client Object and is used by the socket herder to determine whether
-or not the socket connection was cut intentionally.
-
-##### `.has-quit`
-
-Writable `IO::Socket::Async` attribute. Contains an object representing
-the socket connected to the server, although it may already be closed.
-
----
-
-## Client Object (`IRC::Client`)
-
-The Client Object is the heart of this module. The `IRC::Client` you instantiate
-and `.run`. The running Client Object is available to your plugins via
-`.irc` attribute that you can obtain by doing the `IRC::Client::Plugin` role
-or via `.irc` attribute on Message Objects your event handler receives.
-
-The client object's method let you control the client: e.g. joining or parting
-channels, changing nicks, banning users, or sending text messages.
-
-### Methods and Attributes
-
-#### `.join`
-
-```perl6
- $.irc.join: <#foo #bar #ber>, :$server;
-```
-
-Causes the client join the provided channels.
-If `:server` named argument is given, will operate only on that server;
-otherwise operates on all connected servers.
-
-#### `.new`
-
-```perl6
-my $irc = IRC::Client.new:
- :debug
- :host<irc.freenode.net>
- :6667port
- :password<s3cret>
- :channels<#perl #perl6 #rust-lang>
- :nick<MahBot>
- :alias('foo', /b.r/)
- :username<MahBot>
- :userhost<localhost>
- :userreal('Mah awesome bot!')
- :servers(
- freenode => %(),
- local => %( :host<localhost> ),
- )
- :plugins(
- class { method irc-to-me ($ where /42/) { 'The answer to universe!' } }
- )
- :filters(
- -> $text where .lines > 5 or .chars > 300 { "Text is too big!!!" }
- )
-```
-
-Instantiates a new `IRC::Client` object. Takes the following named arguments:
-
-##### `:channels`
-
-```perl6
- :channels('#perl6bot', '#zofbot', '#myown' => 's3cret')
-```
-
-A list of `Str` or `Pair` containing the channels to join.
-Pairs represent channels with channel passwords, where the key is
-the channel and the value is its password.
-**Defaults to:** `#perl6`
-
-##### `:debug`
-
-Takes an `Int`. When set to a positive number, causes debug output to be
-generated. Install optional
-[Terminal::ANSIColor]https://modules.perl6.org/repo/Terminal::ANSIColor] to
-make output colourful. Debug levels:
-
-* `0`—no debug output
-* `1`—basic debug output
-* `2`—also include list of emitted events
-* `3`—also include `irc-all` in the list of emitted events
-
-**Defaults to:** `0`
-
-##### `:filters`
-
-Takes a list of `Callable`s. Will attempt to call them for replies to
-`PRIVMSG` and `NOTICE` events if the signatures accept `($text)` or
-`($text, :$where)` calls, where `$text` is the reply text and `:$where` is
-a named argument of the destination of the reply (either a user or a channel
-name).
-
-Callables with `:$where` in the signature must return two values: the new
-text to reply with and the location. Otherwise, just one value needs to
-be returned: the new text to reply with.
-
-**By default** not specified.
-
-##### `:host`
-
-The hostname of the IRC server to connect to.
-**Defaults to:** `localhost`
-
-##### `:nick`
-
-A list of nicknames to use. If set to just one value will automatically
-generate three additional nicknames that have underscores appended
-(e.g. `P6Bot`, `P6Bot_`, `P6Bot__`, `P6Bot___`).
-
-If one of the given nicks is in use, the client will attempt to use the
-next one in the list.
-
-##### `:alias`
-
-**Defaults to:** `P6Bot`
-
-```perl6
- :alias('foo', /b.r/)
-```
-
-A list of `Str` or `Regex` objects that in the context of
-`irc-addressed`, `irc-to-me`, and `irc-mentioned` events will be used
-as alternative nicks. In other words, specifying `'bot'` as alias will allow
-you to address the bot using `bot` nick, regardless of the actual nick the
-bot is currently using.
-
-**Defaults to:** empty list
-
-##### `:password`
-
-The server password to use. On some networks (like Freenode), the server
-password also functions as NickServ's password.
-**By default** not specified.
-
-##### `:plugins`
-
-Takes a list of instantiated objects or type objects that implement the
-functionality of the system. See [basics tutorial](01-basics.md) and
-[event reference](02-event-reference.md) for more information on how
-to implement plugin classes.
-
-**By default** not specified.
-
-##### `:port`
-
-The port of the IRC server to connect to. Takes an `Int` between 0 and 65535.
-**Defaults to:** `6667`
-
-##### `:servers`
-
-Takes an `Assosiative` with keys as labels of servers and values with
-server-specific configuration. Valid keys in the configuration are
-`:host`, `:port`, `:password`, `:channels`, `:nick`, `:username`,
-`:userhost`, and `:userreal`. They take the same values as same-named arguments
-of the `IRC::Client.new` method and if any key is omitted, the value
-of the `.new`'s argument will be used.
-
-If `:servers` is not specified, then a server will be created with the
-label `_` (underscore).
-
-**By default** not specified.
-
-##### `:username`
-
-The IRC username to use. **Defaults to:** `Perl6IRC`
-
-##### `:userhost`
-
-The hostname of your client. **Defaults to:** `localhost` and can probably
-be left as is, unless you're having issues connecting.
-
-##### `:userreal`
-
-The "real name" of your client. **Defaults to:** `Perl6 IRC Client`
-
-----
-
-#### `.nick`
-
-```perl6
- $.irc.nick: 'MahBot', :$server;
- $.irc.nick: <MahBot MahBot2 MahBot3 MahBot4 MahBot5>, :$server;
-```
-
-Causes the client to change its nick to the one provided and uses this
-as new value of `.current-nick` and `.nick` attributes of the appropriate
-`IRC::Client::Server` object. If only one nick
-is given, another 3 nicks will be automatically generated by appending
-a number of underscores. Will automatically retry nicks further in the list
-if the currently attempted one is already in use.
-
-If `:server` named argument is given, will operate only on that server;
- otherwise operates on all connected servers.
-
-#### `.part`
-
-```perl6
- $.irc.part: <#foo #bar #ber>, :$server;
-```
-
-Causes the client part the provided channels.
-If `:server` named argument is given, will operate only on that server;
-otherwise operates on all connected servers.
-
-#### `.quit`
-
-```perl6
- $.irc.quit;
- $.irc.quit: :$server;
-```
-
-Causes the client to quit the IRC server.
-If `:server` named argument is given, will operate only on that server;
-otherwise operates on all connected servers.
-
-#### `.run`
-
-Takes no arguments. Runs the IRC client, causing it to connect to servers
-and do all of its stuff. Returns only if all of the servers the client connects
-to have been explicitly `.quit` from.
-
-#### `.send`
-
-```perl6
- $.irc.send: :where<Zoffix> :text<Hello!>;
- $.irc.send: :where<#perl6> :text('I ♥ Perl 6!');
- $.irc.send: :where<Senpai> :text('Notice me!') :notice :$server;
-```
-
-Sends a `:text` message to `:where`, using either a `PRIVMSG` or `NOTICE`.
-The `:where` can be either a user's nick or a channel name. If `:notice`
-argument is set to a `True` value will use `NOTICE` otherwise will use
-`PRIVMSG` (if you just want to send text to channel like you'd normally
-do when talking with regular IRC clients, that'd be the `PRIVMSG` messages).
-
-If `:server` named argument is given, will operate only on that server;
-otherwise operates on all connected servers.
-
-**NOTE:** calls to `.send` when server is not connected yet will be
-**semi-silently ignored** (only debug output will mention that they were
-ignored). This is done on purpose, to prevent `.send` calls from interfering
-with the connection negotiation during server reconnects. Although not
-free from race conditions, you can mitigate this issue by checking the
-`.is-connected` attribute on the appropriate `IRC::Client::Server` object
-before attempting the `.send`
-
-## Up Next
-
-Read [Big Picture behaviour](04-big-picture-behaviour.md) next.
diff --git a/docs/04-big-picture-behaviour.md b/docs/04-big-picture-behaviour.md
deleted file mode 100644
index 1f67b84..0000000
--- a/docs/04-big-picture-behaviour.md
+++ /dev/null
@@ -1,44 +0,0 @@
-[[back to doc map]](README.md)
-
-# Big-Picture Behaviour
-
-This document describes the general behaviour of the `IRC::Client` clients.
-
-## Table of Contents
-
-- [Connection Maintenance](#connection-maintenance)
-- [Nickname Maintenance](#nickname-maintenance)
-
----
-
-## Connection Maintenance
-
-The client is designed with the goal of being run indefinitely. As such, it
-will restart server connections if they close.
-
-If a connection fails or a disconnect happens, the client will wait
-10 seconds (non-blockingly) and attempt to reconnect, repeating the process
-if the reconnect fails too. This loop will continue indefinitely until either
-the connection is established or the client explicitly quits the server
-using the `.quit` method on the Client Object.
-
-The described process applies to individual servers, regardless of how many
-servers the client is asked to connect to. Thus, it's possible that one
-server will be in the reconnect loop, while others will be connected and
-functioning like normal. It's also possible for the user to `.quit` some
-servers, while maintaining connection to others. The client object's
-`.run` method will return only when *all* servers have been `.quit`
-
-## Nickname Maintenance
-
-If the first nickname assigned to the bot at the start (or one set
-using `.nick` method) is in use, the bot will automatically use the next one
-in the list. If *all* of the nicks are in use, it will wait a short period
-of time, before retrying all nicks again.
-
-Note: the same system will be applied if the proposed nick is an erroneous
-one that cannot be used on the server—for example, it can contain invalid
-characters or be too long. This means the bot will never succeed
-in connecting to the server or changing a nick if the entire list of nicks it
-possesses are invalid ones. Be sure to turn the debug output on and inspect
-output for any suspect messages if you're having issues connecting.
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index 3a6f153..0000000
--- a/docs/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-[[back to main docs]](../README.md)
-
-# Documentation Map
-
-* [Blog Post](http://perl6.party/post/IRC-Client-Perl-6-Multi-Server-IRC-Module)
-* [Basics Tutorial](01-basics.md)
-* [Event Reference](02-event-reference.md)
-* [Method Reference](03-method-reference.md)
-* [Big-Picture Behaviour](04-big-picture-behaviour.md)
-* [Examples](../examples/)
diff --git a/examples/01-uppercase-bot.p6 b/examples/01-uppercase-bot.p6
deleted file mode 100644
index c432710..0000000
--- a/examples/01-uppercase-bot.p6
+++ /dev/null
@@ -1,8 +0,0 @@
-use lib <lib>;
-use IRC::Client;
-.run with IRC::Client.new:
- :nick<MahBot>
- :host(%*ENV<IRC_CLIENT_HOST> // 'irc.freenode.net')
- :channels<#zofbot>
- :debug
- :plugins(class { method irc-to-me ($_) { .text.uc } })
diff --git a/examples/02-trickster-bot.p6 b/examples/02-trickster-bot.p6
deleted file mode 100644
index 64171a7..0000000
--- a/examples/02-trickster-bot.p6
+++ /dev/null
@@ -1,20 +0,0 @@
-use lib <lib>;
-
-use IRC::Client;
-class Trickster {
- multi method irc-to-me ($ where /time/) { DateTime.now }
- multi method irc-to-me ($ where /temp \s+ $<temp>=\d+ $<unit>=[F|C]/) {
- $<unit> eq 'F' ?? "That's {($<temp> - 32) × .5556}°C"
- !! "That's { $<temp> × 1.8 + 32 }°F"
- }
-}
-
-class BFF { method irc-to-me ($ where /'♥'/) { 'I ♥ YOU!' } }
-
-.run with IRC::Client.new:
- :nick<MahBot>
- :alias('foo', /b.r/)
- :host(%*ENV<IRC_CLIENT_HOST> // 'irc.freenode.net')
- :channels<#zofbot>
- :debug
- :plugins(Trickster, BFF)
diff --git a/examples/03-github-notifications.p6 b/examples/03-github-notifications.p6
deleted file mode 100644
index 58596c6..0000000
--- a/examples/03-github-notifications.p6
+++ /dev/null
@@ -1,38 +0,0 @@
-use lib <lib>;
-
-use IRC::Client;
-use HTTP::Tinyish;
-use JSON::Fast;
-
-class GitHub::Notifications does IRC::Client::Plugin {
- has Str:D $.token = %*ENV<GITHUB_TOKEN>;
- has $!ua = HTTP::Tinyish.new;
- constant $API_URL = 'https://api.github.com/notifications';
-
- method irc-connected ($) {
- start react {
- whenever self!notification.grep(* > 0) -> $num {
- $.irc.send: :where<Zoffix>
- :text("You have $num unread notifications!")
- :notice;
- }
- }
- }
-
- method !notification {
- supply {
- loop {
- my $res = $!ua.get: $API_URL, :headers{ :Authorization("token $!token") };
- $res<success> and emit +grep *.<unread>, |from-json $res<content>;
- sleep $res<headers><X-Poll-Interval> || 60;
- }
- }
- }
-}
-
-.run with IRC::Client.new:
- :nick<MahBot>
- :host(%*ENV<IRC_CLIENT_HOST> // 'irc.freenode.net')
- :channels<#zofbot>
- :debug
- :plugins(GitHub::Notifications.new)
diff --git a/examples/04-bash-bot.p6 b/examples/04-bash-bot.p6
deleted file mode 100644
index 53baf87..0000000
--- a/examples/04-bash-bot.p6
+++ /dev/null
@@ -1,26 +0,0 @@
-use lib <lib>;
-
-use IRC::Client;
-use Mojo::UserAgent:from<Perl5>;
-
-class Bash {
- constant $BASH_URL = 'http://bash.org/?random1';
- constant $cache = Channel.new;
- has $!ua = Mojo::UserAgent.new;
-
- multi method irc-to-me ($ where /bash/) {
- start $cache.poll or do { self!fetch-quotes; $cache.poll };
- }
-
- method !fetch-quotes {
- $cache.send: $_
- for $!ua.get($BASH_URL).res.dom.find('.qt').each».all_text.lines.join: ' ';
- }
-}
-
-.run with IRC::Client.new:
- :nick<MahBot>
- :host(%*ENV<IRC_CLIENT_HOST> // 'irc.freenode.net')
- :channels<#zofbot>
- :debug
- :plugins(Bash.new);
diff --git a/examples/05-bash-bot-with-filter.p6 b/examples/05-bash-bot-with-filter.p6
deleted file mode 100644
index f2aa692..0000000
--- a/examples/05-bash-bot-with-filter.p6
+++ /dev/null
@@ -1,32 +0,0 @@
-use lib <lib>;
-
-use IRC::Client;
-use Pastebin::Shadowcat;
-use Mojo::UserAgent:from<Perl5>;
-
-class Bash {
- constant $BASH_URL = 'http://bash.org/?random1';
- constant $cache = Channel.new;
- has $!ua = Mojo::UserAgent.new;
-
- multi method irc-to-me ($ where /bash/) {
- start $cache.poll or do { self!fetch-quotes; $cache.poll };
- }
-
- method !fetch-quotes {
- $cache.send: $_
- for $!ua.get($BASH_URL).res.dom.find('.qt').each».all_text;
- }
-}
-
-.run with IRC::Client.new:
- :nick<MahBot>
- :host(%*ENV<IRC_CLIENT_HOST> // 'irc.freenode.net')
- :channels<#zofbot>
- :debug
- :plugins(Bash.new)
- :filters(
- -> $text where .lines > 1 || .chars > 300 {
- Pastebin::Shadowcat.new.paste: $text.lines.join: "\n";
- }
- )
diff --git a/examples/06-multi-server.p6 b/examples/06-multi-server.p6
deleted file mode 100644
index 3a5f5fc..0000000
--- a/examples/06-multi-server.p6
+++ /dev/null
@@ -1,23 +0,0 @@
-use lib <lib>;
-
-use IRC::Client;
-
-class BFF {
- method irc-to-me ($ where /'♥'/) { 'I ♥ YOU!' }
-}
-
-.run with IRC::Client.new:
- :debug
- :plugins(BFF)
- :nick<MahBot>
- :channels<#zofbot>
- :servers(
- freenode => %(
- :host<irc.freenode.net>,
- ),
- local => %(
- :nick<P6Bot>,
- :channels<#zofbot #perl6>,
- :host<localhost>,
- )
- )
diff --git a/examples/07-multi-server-message-forwarder.p6 b/examples/07-multi-server-message-forwarder.p6
deleted file mode 100644
index 06d07ec..0000000
--- a/examples/07-multi-server-message-forwarder.p6
+++ /dev/null
@@ -1,37 +0,0 @@
-use lib <lib>;
-
-use IRC::Client;
-
-class Messenger does IRC::Client::Plugin {
- method irc-privmsg-channel ($e) {
- for $.irc.servers.values -> $server {
- for $server.channels -> $channel {
- next if $server eq $e.server and $channel eq $e.channel;
-
- $.irc.send: :$server, :where($channel), :text(
- "$e.nick() over at $e.server.host()/$e.channel() says $e.text()"
- );
- }
- }
-
- $.irc.send: :where<Zoffix>
- :text('I spread the messages!')
- :server<local>;
- }
-}
-
-.run with IRC::Client.new:
- :debug
- :plugins[Messenger.new]
- :nick<MahBot>
- :channels<#zofbot>
- :servers{
- freenode => %(
- :host<irc.freenode.net>,
- ),
- local => %(
- :nick<P6Bot>,
- :channels<#zofbot #perl6>,
- :host<localhost>,
- )
- }
diff --git a/examples/08-numeric-bot.p6 b/examples/08-numeric-bot.p6
deleted file mode 100644
index c8edac9..0000000
--- a/examples/08-numeric-bot.p6
+++ /dev/null
@@ -1,32 +0,0 @@
-use lib <lib>;
-use IRC::Client;
-
-.run with IRC::Client.new:
- :nick<MahBot>
- :host(%*ENV<IRC_CLIENT_HOST> // 'irc.freenode.net')
- :channels<#zofbot>
- :2debug
- :plugins(class :: does IRC::Client::Plugin {
- my class NameLookup { has $.channel; has @.users; has $.e; }
- has %.lookups of NameLookup;
-
- method irc-to-me ($e where /^ 'users in ' $<channel>=\S+/) {
- my $channel = ~$<channel>;
- return 'Look up of this channel is already in progress'
- if %!lookups{$channel};
-
- %!lookups{$channel} = NameLookup.new: :$channel :$e;
- $.irc.send-cmd: 'NAMES', $channel;
- Nil;
- }
- method irc-n353 ($e where so %!lookups{ $e.args[2] }) {
- %!lookups{ $e.args[2] }.users.append: $e.args[3].words;
- Nil;
- }
- method irc-n366 ($e where so %!lookups{ $e.args[1] }) {
- my $lookup = %!lookups{ $e.args[1] }:delete;
- $lookup.e.reply: "Users in $lookup.channel(): $lookup.users()[]";
- Nil;
- }
-
- }.new)
diff --git a/examples/README.md b/examples/README.md
deleted file mode 100644
index 49b9056..0000000
--- a/examples/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-[[back to doc map]](../docs/README.md)
-
-# Examples
-
-These examples are covered in [the IRC::Client blog
-post](http://perl6.party/post/IRC-Client-Perl-6-Multi-Server-IRC-Module)
-
-* [Uppercasing bot](01-uppercase-bot.p6)
-* [Small bot with a couple of features](02-trickster-bot.p6)
-* [Bot to announce GitHub notifications](03-github-notifications.p6)
-* [Bash.org bot](04-bash-bot.p6)
-* [Bash.org bot with pastebin filter](05-bash-bot-with-filter.p6)
-* [Multi-server bot](06-multi-server.p6)
-* [Multi-server message forwarder bot](07-multi-server-message-forwarder.p6)
diff --git a/historical-archive/DESIGN/01-main.md b/historical-archive/DESIGN/01-main.md
deleted file mode 100644
index 196fc1c..0000000
--- a/historical-archive/DESIGN/01-main.md
+++ /dev/null
@@ -1,961 +0,0 @@
-# TABLE OF CONTENTS
-- [PURPOSE](#purpose)
-- [GOALS](#goals)
- - [Ease of Use](#ease-of-use)
- - [Client-Generated Events](#client-generated-events)
- - [Possibility of Non-Blocking Code](#possibility-of-non-blocking-code)
-- [DESIGN](#design)
-- [Multi-Server Interface](#multi-server-interface)
-- [Client Object](#client-object)
- - [`$.irc` (access from inside a plugin)](#irc-access-from-inside-a-plugin)
- - [`.new`](#new)
- - [`.run`](#run)
- - [`.quit`](#quit)
- - [`.part`](#part)
- - [`.join`](#join)
- - [`.send`](#send)
- - [`.nick`](#nick)
- - [`.emit`](#emit)
- - [`.emit-custom`](#emit-custom)
- - [`.channel`](#channel)
- - [`.has`](#has)
- - [`.topic`](#topic)
- - [`.modes`](#modes)
- - [`.bans`](#bans)
- - [`.names`](#names)
-- [Message Delivery](#message-delivery)
-- [Response Constants](#response-constants)
- - [`IRC_NEXT`](#irc_next)
- - [`IRC_DONE`](#irc_done)
-- [Message Object Interface](#message-object-interface)
- - [`.nick`](#nick-1)
- - [`.username`](#username)
- - [`.host`](#host)
- - [`.usermask`](#usermask)
- - [`.reply`](#reply)
-- [Convenience Events](#convenience-events)
- - [`irc-to-me`](#irc-to-me)
- - [`irc-addressed`](#irc-addressed)
- - [`irc-mentioned`](#irc-mentioned)
- - [`irc-privmsg-channel`](#irc-privmsg-channel)
- - [`irc-privmsg-me`](#irc-privmsg-me)
- - [`irc-notice-channel`](#irc-notice-channel)
- - [`irc-notice-me`](#irc-notice-me)
- - [`irc-started`](#irc-started)
- - [`irc-connected`](#irc-connected)
- - [`irc-mode-channel`](#irc-mode-channel)
- - [`irc-mode-user`](#irc-mode-user)
- - [`irc-all`](#irc-all)
-- [Numeric Events](#numeric-events)
-- [Named Events](#named-events)
- - [`irc-nick`](#irc-nick)
- - [`irc-quit`](#irc-quit)
- - [`irc-join`](#irc-join)
- - [`irc-part`](#irc-part)
- - [`irc-mode`](#irc-mode)
- - [`irc-topic`](#irc-topic)
- - [`irc-invite`](#irc-invite)
- - [`irc-kick`](#irc-kick)
- - [`irc-privmsg`](#irc-privmsg)
- - [`irc-notice`](#irc-notice)
-- [Custom Events](#custom-events)
-
-# PURPOSE
-
-The purpose of IRC::Client is to serve as a fully-functional IRC
-client that--unlike programs like HexChat or mIRC--provide a programmatic
-interface to IRC. So, for example, to send a message to a channel, instead
-of typing a message in a message box and pressing ENTER, a method is called
-and given a string.
-
-Naturally, such an interface provides vast abilities to automate interactions
-with IRC or implement a human-friendly interface, such as HexChat or mIRC.
-
-# GOALS
-
-An implementation must achieve these goals:
-
-## Ease of Use
-
-For basic use, such as a bot that responds to triggers said in channel,
-the details of the IRC protocol must be as invisible as possible. Just as any
-user can install HexChat and join a channel and talk, similar usability has
-to be achieved by the implementation.
-
-As an example, a HexChat user can glance at the user list or channel topic
-without explicitly issuing `NAMES` or `TOPIC` IRC commands. The implementation
-should thus provide similar simplicity and provide a userlist or topic
-via a convenient method rather than explicit method to send the appropriate
-commands and the requirement of listening for the server response events.
-
-## Client-Generated Events
-
-The implementation must allow the users of the code to emit IRC and custom
-events. For example, given plugins A and B, with A performing processing
-first, plugin A can mark all `NOTICE` IRC events as handled and emit them
-as `PRIVMSG` events instead. From the point of view of second plugin B, no
-`NOTICE` commands ever happen (as they arrive to it as `PRIVMSG`).
-
-Similarly, plugin A can choose to emit custom event `FOOBAR` instead of
-`PRIVMSG`, to which plugin B can choose to respond to.
-
-## Possibility of Non-Blocking Code
-
-The implementation must allow the user to perform responses to events in
-a non-blocking manner if they choose to.
-
-# DESIGN
-
-The implementation consists of Core code responsible for maintaining the
-state of the connected client, parsing of server messages, and sending
-essential messages, as well as relating messages to and from plugins.
-
-The implementation distribution may also include several plugins that may
-be commonly needed by users. Such plugins are not enabled by default and
-the user must request their inclusion with code.
-
-# Multi-Server Interface
-
-The interface described in the rest of this document assumes a connection
-to a single server. Should the client be connected to multiple-servers at
-the time, issuing commands described will apply to *every* connected server.
-A server must be specified to issue a command to a single server.
-**Plugin authors must keep this fact in mind, when writing plugins, as
-forgetting to handle multiple servers can result in unwanted behaviour.**
-
-The same reasoning applies to the `.new` method: attributes, such as
-nicknames, usernames, etc. given without associating them with a server will
-apply to ALL connected servers. Configuration for individual servers is
-given via `:servers` named parameter as a list of `Pairs`. The key
-is the nickname of server and must be a valid method name. It's recommended
-to choose something that won't end up an actual method on the Client Object.
-It's guaranteed methods starting with `s-` will always be safe to use. The
-value is a list of pairs that can be accepted by the Client Object as named
-parameters (except for `:servers`) that specify the configuration for that
-specific server, overriding any of the non-server-specific parameters already
-set.
-
-A possible `.new` setup may look something like this:
-
-```perl6
- my $irc = IRC::Client.new:
- :nick<ZofBot ZofBot_ ZofBot__> # nicks to try to use on ALL servers,
- :servers(
- s-leliana => (
- :server<irc.freenode.net>,
- :channels<#perl #perl6 #perl7>
- ),
- s-morrigan => (
- :server<irc.perl.org>,
- :channels<#perl #perl-help>
- ),
- s-alistair => (
- :nick<Party Party_ Party__> # nick override
- :server<irc.perl6.pary>,
- :channels<#perler>
- ),
- ),
-```
-
-Use of multiple servers is facilitated via server nicknames and using
-them as a method call to obtain the correct Client Object. For example:
-
-```perl6
- $.irc.quit; # quits all servers
- $.irc.s-leliana.quit; # quits only the s-leliana server
-
- # send a message to #perl6 channel on s-morrigan server
- $.irc.s-morrigan.send: where => '#perl6', text => 'hello';
-```
-
-The Message Object will also contain a `.server` method value of which
-is the nickname of the server from which the message arrived. In general,
-the most common way to generate messages will be using `.reply` on the Message
-Object, making the multi-server paradigm completely transparent.
-
-# Client Object
-
-Client Object represents a connected IRC client and is aware of and can
-manipulate its state, such as disconnecting, joining or parting a channel,
-or sending messages.
-
-A Client Object must support the ability to connect to multiple servers.
-The client object provides these methods:
-
-## `$.irc` (access from inside a plugin)
-
-```perl6
- use IRC::Client::Plugin;
- unit Plugin::Foo is IRC::Client::Plugin;
-
- method irc-privmsg-me ($msg) {
- $.irc.send:
- where => '#perl6',
- text => "$msg.nick() just sent me a secret! It's $msg.text()";
- }
-```
-
-A plugin inherits from `IRC::Client::Plugin`, which provides `$.irc`
-attribute containing the Client Object, allowing the plugin to utilize all
-of the methods it provides.
-
-## `.new`
-
-```perl6
- my $irc = IRC::Client.new:
- ...
- :plugins(
- IRC::Client::Plugin::Factoid.new,
- My::Plugin.new,
- class :: is IRC::Client::Plugin {
- method irc-privmsg-me ($msg) { $msg.repond: 'Go away!'; }
- },
- );
-```
-
-*Not to be used inside plugins.*
-Creates a new `IRC::Client` object. Along with the usual arguments like
-nick, username, server address, etc, takes `:plugins` argument that
-lists the plugins to include. All messages will be propagated through plugins
-in the order they are defined here.
-
-## `.run`
-
-```perl6
- $irc.run;
-```
-
-*Not to be used inside plugins.*
-Starts the client, connecting to the server and maintaining that connection
-and not returning until an explicit `.quit` is issued. If the connection
-breaks, the client will attempt to reconnect.
-
-## `.quit`
-
-```perl6
- $.irc.quit;
-
- $.irc.quit: 'Reason';
-```
-
-Disconnects from the server. Takes an option string to be given to the
-server as the reson for quitting.
-
-## `.part`
-
-```per6
- $.irc.part: '#perl6';
-
- $.irc.part: '#perl6', 'Leaving';
-```
-
-Exits a channel. Takes two positional strings: the channel to part
-and an optional parting message. Causes the client object to discard any state
-kept for this channel.
-
-## `.join`
-
-```perl6
- $.irc.join '#perl6', '#perl7';
-```
-
-Attempts to joins channels given as positional arguments.
-
-## `.send`
-
-```perl6
- $.irc.send: where => '#perl6', text => 'Hello, Perl 6!';
-
- $.irc.send: where => 'Zoffix', text => 'Hi, Zoffie!';
-
- $.irc.send: where => 'Zoffix', text => 'Notice me, senpai!', :notice;
-```
-
-Sends a message specified by `text` argument
-either to a user or a channel specified by `:where` argument. If `Bool`
-argument `:notice` is set to true, will send a *notice* instead of regular
-message.
-
-Note that in IRC bots that respond to commands from other users a more
-typical way to reply to those commands would be by calling
-`.reply` method on the Message Object, rather than using `.send` method.
-
-
-## `.nick`
-
-```perl6
- $.irc.nick: 'ZofBot', 'ZofBot_', 'ZofBot__';
-```
-
-Attempts to change the nick of the client. Takes one or more positional
-arguments that are a list of nicks to try.
-
-## `.emit`
-
-```perl6
- $.irc.emit: $msg;
-
- $.irc.emit: IRC::Client::Message::Privmsg.new:
- nick => 'Zoffix',
- text => 'Hello',
- ...;
- ...
- method irc-privmsg ($msg) {
- say "$msg.nick() said $msg.text()... or did they?";
- }
-```
-
-Takes an object of any of `IRC::Client::Message::*` subclass and emits it
-as if it were a new event. That is, it will propagate through the plugin chain
-starting at the first plugin, and not the one emiting the event, and the
-plugins can't tell whether the message is self-generated or something that
-came from the server.
-
-## `.emit-custom`
-
-```perl6
- $.irc.emit-custom: 'my-event', 'just', 'some', :args;
-```
-
-Same idea as `.emit`, except a custom event is emitted. The first positional
-argument specifies the name of the event to emit. Any other arguments
-given here will be passed as is to listener methods.
-
-## `.channel`
-
-```perl6
- method irc-addressed ($msg) {
- if $msg.text ~~ /'kick' \s+ $<nick>=\S+/ {
- $msg.reply: "I don't see $<nick> up in here"
- unless $.irc.channel($msg.channel).?has: ~$<nick>;
- }
-
- if $msg.text ~~ /'topic' \s+ $<channel>=\S+/ {
- return $msg.reply: $_
- ?? "Channel $<channel> does not exist"
- !! "Topic in $<channel> is $_.topic()"
- given $.irc.channel: ~$<channel>;
- }
- }
-```
-
-Returns an `IRC::Client::Channel` object for the channel given as positional
-argument, or `False` if no such channel seems to exist. Unless our client is
-currently *on* that channel, that existence is
-determined with `LIST` IRC command, so there will be some false negatives,
-such as when attempting to get an object for a channel with secret mode set.
-
-The Client Object tracks state for any of the joined channels, so some
-information obtainable via the Channel Object will be cached
-and retrieved from that state, whenever possible. Otherwise, a request
-to the server will be generated. Return values will be empty (empty lists
-or empty strings) when requests fail. The channel object provides the
-following methods.
-
-### `.has`
-
-```perl6
- $.irc.channel('#perl6').has: 'Zoffix';
-```
-
-Returns `True` or `False` indicating whether a user with the given nick is
-present on the channel.
-
-### `.topic`
-
-```perl6
- say "Topic of the channel is " ~ $.irc.channel('#perl6').topic;
-```
-
-Returns the `TOPIC` of the channel.
-
-### `.modes`
-
-```perl6
- say $.irc.channel('#perl6').modes;
- # ('s', 'n', 't')
-```
-
-Returns a list of single-letter codes for currently active channel modes
-on the channel. Note, this does not include any bans.
-
-### `.bans`
-
-```perl6
- say $.irc.channel('#perl6').bans;
- # ('*!spammer@*', 'warezbot!*@*')
-```
-
-Returns a list of currently active ban masks on the channel.
-
-### `.names`
-
-```perl6
- say $.irc.channel('#perl6').names;
- # ('@Zoffix', '+zoffixs-helper', 'not-zoffix')
-```
-
-Returns a list of nicks present on the channel, each potentially prefixed
-with a [channel membership prefix](https://www.alien.net.au/irc/chanmembers.html)
-
-# Message Delivery
-
-An event listener is defined by a method in a plugin class. The name
-of the method starts with `irc-` and followed by the lowercase name of the
-event. User-defined events follow the same pattern, except they start with
-`irc-custom-`:
-
-```perl6
- use IRC::Client::Plugin;
- unit Plugin::Foo is IRC::Client::Plugin;
-
- # Listen to PRIVMSG IRC events:
- method irc-privmsg ($msg) {
- return IRC_NEXT unless $msg.channel eq '#perl6';
- $msg.reply: 'Nice to meet you!';
- }
-
- # Listen to custom client-generated events:
- method irc-custom-my-event ($some, $random, :$args) {
- return IRC_NEXT unless $random > 5;
- $.irc.send: where => '#perl6', text => 'Custom event triggered!';
- }
-```
-
-An event listener receives the event message in the form of an object.
-The object must provide all the relevant information about the source
-and content of the message.
-
-The message object, where appropriate, must provide a means to send a reply
-back to the originator of the message. For example, here's a potential
-implementation of `PRIVMSG` handler that receives the message object:
-
-```perl6
- method irc-privmsg-channel ($msg) {
- return IRC_NEXT unless $msg.channel eq '#perl6';
- $msg.reply: 'Nice to meet you!';
- }
-```
-
-A plugin can send messages and emit events at will:
-
-```perl6
- method irc-connected {
- Supply.interval(60).tap: {
- $.irc.send: where => '#perl6', text => 'One minute passed!!';
- };
- Promise.in(60*60).then: {
- $.irc.send:
- where => 'Zoffix',
- text => 'I lived for one hour already!',
- :notice;
-
- $.irc.emit-custom: 'MY-EVENT', 'One hour passed!';
- }
- }
-```
-
-# Response Constants
-
-Multiple plugins can listen to the same event. The event message will be
-handed to each of the plugins in the sequence they are defined when the
-Client Object is initialized. Each handler can use predefined response
-constants to signal whether the handling of this particular event message
-should stop or continue onto the next plugin. These response constants
-are `IRC_NEXT` and `IRC_DONE` and are exported by `IRC::Client::Plugin`.
-
-## `IRC_NEXT`
-
-```perl6
- method irc-privmsg-channel ($msg) {
- return IRC_NEXT unless $msg.channel eq '#perl6';
- ....
- }
-```
-
-Signals that the message should continue to be passed on to any further
-plugins that subscribed to handle it.
-
-## `IRC_DONE`
-
-```perl6
- method irc-privmsg-channel ($msg) {
- return IRC_DONE if $msg.channel eq '#perl6';
- }
-
- # or just...
-
- method irc-privmsg-channel ($msg) {}
-```
-
-Signals that the message has been handled and should NOT be passed on
-to any further plugins. **Note:** you don't have to explicitly return this
-value; anything other than returning `IRC_NEXT` is the same as returning
-`IRC_DONE`.
-
-
-# Message Object Interface
-
-The message object received by all non-custom events is an event-specific
-subclass of `IRC::Client::Message`. The subclass is named
-`IRC::Client::Message::$NAME`, where `$NAME` is:
-
-* *Named* and *Convenience* events use their names without `irc-` part, with any `-`
-changed to `::` and with each word written in `Title Case`. e.g.
-message object for `irc-privmsg-me` is `IRC::Client::Message::Privmsg::Me`
-* *Numeric* events always receive `IRC::Client::Message::Numeric` message
-object, regardless of the actual number of the event.
-
-Along with event-specific methods
-described under each event, the `IRC::Client::Message` offers the following
-methods:
-
-## `.nick`
-
-```perl6
- say $msg.nick ~ " says hello";
-```
-
-Contains the nickname of the sender of the message.
-
-## `.username`
-
-```perl6
- say $msg.nick ~ " has username " ~ $msg.username;
-```
-
-Contains the username of the sender of the message.
-
-## `.host`
-
-```perl6
- say $msg.nick ~ " is connected from " ~ $msg.host;
-```
-
-Hostname of sender of the message.
-
-## `.usermask`
-
-```perl6
- say $msg.usermask;
-```
-
-Nick, username, and host combined into a full usermask, e.g.
-`Zoffix!zoffix@zoffix.com`
-
-## `.reply`
-
-```perl6
- $msg.reply: 'I love you too'
- if $msg.text ~~ /'I love you'/;
-```
-
-Replies back to a message. For example, if we received the message as a
-private message to us, the reply will be a private message back to the
-user. Same for notices. For in-channel messages, `irc-addressed`
-and `irc-to-me` will address the sender in return, while all other in-channel
-events will not.
-
-**NOTE:** this method is only available for these events:
-
-* `irc-privmsg`
-* `irc-notice`
-* `irc-to-me`
-* `irc-addressed`
-* `irc-mentioned`
-* `irc-privmsg-channel`
-* `irc-privmsg-me`
-* `irc-notice-channel`
-* `irc-privmsg-me`
-
-# Convenience Events
-
-These sets of events do not have a corresponding IRC command defined by the
-protocol and instead are offered to make listening for a specific kind
-of events easier.
-
-## `irc-to-me`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 PRIVMSG zoffix2 :hello
- # :zoffix!zoffix@127.0.0.1 NOTICE zoffix2 :hello
- # :zoffix!zoffix@127.0.0.1 PRIVMSG #perl6 :zoffix2, hello
-
- method irc-to-me ($msg) {
- printf "%s told us `%s` using %s\n",
- .nick, .text, .how given $msg;
- }
-```
-
-Emitted when a user sends us a message as a private message, notice, or
-addresses us in a channel. The `.respond` method of the Message
-Object is the most convenient way to respond back to the sender of the message.
-
-The `.how` method returns a `Pair` where the key is the message type used
-(`PRIVMSG` or `NOTICE`) and the value is the addressee of that message
-(a channel or us).
-
-## `irc-addressed`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 PRIVMSG #perl6 :zoffix2, hello
-
- method irc-addressed ($msg) {
- printf "%s told us `%s` in channel %s\n",
- .nick, .text, .channel given $msg;
- }
-```
-
-Emitted when a user addresses us in a channel. Specifically, this means
-their message starts with our nickname, followed by optional comma or colon,
-followed by whitespace. That prefix will be stripped from the message.
-
-## `irc-mentioned`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 PRIVMSG #perl6 :Is zoffix2 a robot?
-
- method irc-mentioned ($msg) {
- printf "%s mentioned us in channel %s when they said %s\n",
- .nick, .channel, .text given $msg;
- }
-```
-
-Emitted when a user mentions us in a channel. Specifically, this means
-their message contains our nickname separated by a word boundary on each side.
-
-## `irc-privmsg-channel`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 PRIVMSG #perl6 :hello
-
- method irc-privmsg-channel ($msg) {
- printf "%s said `%s` to channel %s\n",
- .nick, .text, .channel given $msg;
- }
-```
-
-Emitted when a user sends a message to a channel.
-
-## `irc-privmsg-me`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 PRIVMSG zoffix2 :hey bruh
-
- method irc-privmsg-me ($msg) {
- printf "%s messaged us: %s\n", .nick, .text given $msg;
- }
-```
-
-Emitted when a user sends us a private message.
-
-## `irc-notice-channel`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 NOTICE #perl6 :Notice me!
-
- method irc-notice-channel ($msg) {
- printf "%s sent a notice `%s` to channel %s\n",
- .nick, .text, .channel given $msg;
- }
-```
-
-Emitted when a user sends a notice to a channel.
-
-## `irc-notice-me`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 NOTICE zoffix2 :did you notice me?
-
- method irc-notice-me ($msg) {
- printf "%s sent us a notice: %s\n", .nick, .text given $msg;
- }
-```
-
-Emitted when a user sends us a private notice.
-
-## `irc-started`
-
-```perl6
- method irc-started {
- $.do-some-sort-of-init-setup;
- }
-```
-
-Emitted when the IRC client is started. Useful for doing setup work, like
-initializing database connections, etc. Note: this event will fire only once,
-even if the client reconnects to the server numerous times. Note that
-unlike most events, this event does *not* receive a Message Object.
-**IMPORTANT:** when this event fires, there's no guarantee we even started a
-connection to the server, let alone connected successfully.
-
-## `irc-connected`
-
-```perl6
- method irc-connected {
- $.do-some-sort-of-per-connection-setup;
- }
-```
-
-Similar to `irc-started`, except will be emitted every time a
-*successful* connection to the server is made and we joined all
-of the requested channels. That is, we'll wait to either receive the
-full user list or error message for each of the channels we're joining.
-Note that unlike most events, this event does *not* receive a Message Object.
-
-## `irc-mode-channel`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 MODE #perl6 +o zoffix2
- # :zoffix!zoffix@127.0.0.1 MODE #perl6 +bbb Foo!*@* Bar!*@* Ber!*@*
-
- method irc-mode-channel ($msg) {
- printf "Nick %s with usermask %s set mode(s) %s in channel %s\n",
- .nick, .usermask, .modes, .channel given $msg;
- }
-```
-
-Emitted when IRC `MODE` command is received and it's being operated on a
-channel, see `irc-mode` event for details.
-
-## `irc-mode-user`
-
-```perl6
- # :zoffix2!f@127.0.0.1 MODE zoffix2 +w
-
- method irc-mode-user ($msg) {
- printf "Nick %s with usermask %s set mode(s) %s on user %s\n",
- .nick, .usermask, .modes, .who given $msg;
- }
-```
-
-Emitted when IRC `MODE` command is received and it's being operated on a
-user, see `irc-mode` event for details.
-
-## `irc-all`
-
-```perl6
- method irc-all ($msg) {
- say "Received an event: $msg.perl()";
- return IRC_NEXT;
- }
-```
-
-Emitted for all events and is mostly useful for debugging. The type of the
-message object received will depend on the type of the event that generated
-the message. This event will be triggered *AFTER* all other event handlers
-in the current plugin are processed.
-
-# Numeric Events
-
-Numeric IRC events can be subscribed to by defining a method with name
-`irc-` followed by the numeric code of the event (e.g. `irc-001`). The
-arguments of the event can be accessed via `.args` method that returns a
-list of strings:
-
-```perl6
- method irc-004 ($msg) {
- say "Here are the arguments of the RPL_MYINFO event:";
- .say for $msg.args;
- }
-```
-
-See [this reference](https://www.alien.net.au/irc/irc2numerics.html) for
-a detailed list of numerics and their arguments available in the wild. Note:
-the client will emit an event for any received numeric with a 3-digit
-code, regardless of whether it is listed in that reference.
-
-# Named Events
-
-## `irc-nick`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 NICK not-zoffix
-
- method irc-nick ($msg) {
- printf "%s changed nickname to %s\n", .nick, .new-nick given $msg;
- }
-```
-
-[RFC 2812, 3.1.2](https://tools.ietf.org/html/rfc2812#section-3.1.2).
-Emitted when a user changes their nickname.
-
-## `irc-quit`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 QUIT :Quit: Leaving
-
- method irc-quit ($msg) {
- printf "%s has quit (%s)\n", .nick, .reason given $msg;
- }
-```
-
-[RFC 2812, 3.1.7](https://tools.ietf.org/html/rfc2812#section-3.1.7).
-Emitted when a user quits the server.
-
-## `irc-join`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 JOIN :#perl6
-
- method irc-join ($msg) {
- printf "%s joined channel %s\n", .nick, .channel given $msg;
- }
-```
-
-[RFC 2812, 3.2.1](https://tools.ietf.org/html/rfc2812#section-3.2.1).
-Emitted when a user joins a channel.
-
-## `irc-part`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 PART #perl6 :Leaving
-
- method irc-part ($msg) {
- printf "%s left channel %s (%s)\n", .nick, .channel, .reason given $msg;
- }
-```
-
-[RFC 2812, 3.2.2](https://tools.ietf.org/html/rfc2812#section-3.2.2).
-Emitted when a user leaves a channel.
-
-## `irc-mode`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 MODE #perl6 +o zoffix2
- # :zoffix!zoffix@127.0.0.1 MODE #perl6 +bbb Foo!*@* Bar!*@* Ber!*@*
- # :zoffix2!f@127.0.0.1 MODE zoffix2 +w
-
- method irc-mode ($msg) {
- if $msg.?channel {
- # channel mode change
- printf "%s set mode(s) %s in channel %s\n",
- .nick, .modes, .channel given $msg;
- }
- else {
- # user mode change
- printf "%s set mode(s) %s on user %s\n",
- .nick, .modes, .who given $msg;
- }
- }
-```
-
-[RFC 2812, 3.1.5](https://tools.ietf.org/html/rfc2812#section-3.1.5)/[RFC 2812, 3.2.3](https://tools.ietf.org/html/rfc2812#section-3.2.3).
-Emitted when IRC `MODE` command is received. As the command is dual-purpose,
-the message object will have either `.channel` method available
-(for channel mode changes) or `.who` method (for user mode changes). See
-also `irc-mode-channel` and `irc-mode-user` convenience events.
-
-For channel modes, the `.modes` method returns a list of `Pair` where key
-is the mode set and the value is the argument for that mode (i.e. "limit",
-"user", or "banmask") or an empty string if the mode takes no arguments.
-
-For user modes, the `.modes` method returns a list of `Str` of the modes
-set.
-
-The received message object will be one of the subclasses of
-`IRC::Client::Message::Mode` object: `IRC::Client::Message::Mode::Channel`
-or `IRC::Client::Message::Mode::User`.
-
-## `irc-topic`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 TOPIC #perl6 :meow
-
- method irc-topic ($msg) {
- printf "%s set topic of channel %s to %s\n",
- .nick, .channel, .topic given $msg;
- }
-```
-
-[RFC 2812, 3.2.4](https://tools.ietf.org/html/rfc2812#section-3.2.4).
-Emitted when a user changes the topic of a channel.
-
-## `irc-invite`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 INVITE zoffix2 :#perl6
-
- method irc-invite ($msg) {
- printf "%s invited us to channel %s\n", .nick, .channel given $msg;
- }
-```
-
-[RFC 2812, 3.2.7](https://tools.ietf.org/html/rfc2812#section-3.2.7).
-Emitted when a user invites us to a channel.
-
-## `irc-kick`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 KICK #perl6 zoffix2 :go away
-
- method irc-kick ($msg) {
- printf "%s kicked %s out of %s (%s)\n",
- .nick, .who, .channel, .reason given $msg;
- }
-```
-
-[RFC 2812, 3.2.8](https://tools.ietf.org/html/rfc2812#section-3.2.8).
-Emitted when someone kicks a user out of a channel.
-
-## `irc-privmsg`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 PRIVMSG #perl6 :hello
- # :zoffix!zoffix@127.0.0.1 PRIVMSG zoffix2 :hey bruh
-
- method irc-privmsg ($msg) {
- if $msg.?channel {
- # message sent to a channel
- printf "%s said `%s` to channel %s\n",
- .nick, .text, .channel given $msg;
- }
- else {
- # private message
- printf "%s messaged us: %s\n", .nick, .text given $msg;
- }
- }
-```
-
-[RFC 2812, 3.3.1](https://tools.ietf.org/html/rfc2812#section-3.3.1).
-Emitted when a user sends a message either to a channel
-or a private message to us. See *Convenience Events* section for a number
-of more convenient ways to listen to messages.
-
-## `irc-notice`
-
-```perl6
- # :zoffix!zoffix@127.0.0.1 NOTICE #perl6 :Notice me!
- # :zoffix!zoffix@127.0.0.1 NOTICE zoffix2 :did you notice me?
-
- method irc-notice ($msg) {
- if $msg.?channel {
- # notice sent to a channel
- printf "%s sent a notice `%s` to channel %s\n",
- .nick, .text, .channel given $msg;
- }
- else {
- # private notice
- printf "%s sent us a notice: %s\n", .nick, .text given $msg;
- }
- }
-```
-
-[RFC 2812, 3.3.2](https://tools.ietf.org/html/rfc2812#section-3.3.2).
-Emitted when a user sends a notice either to a channel
-or a private notice to us. See *Convenience Events* section for a number
-of more convenient ways to listen to notices and messages.
-
-# Custom Events
-
-There is support for custom events. A custom event is emitted by calling
-`.emit-custom` method on the Client Object and is subscribed to via
-`irc-custom-*` methods:
-
-```perl6
- $.irc.emit-custom: 'my-event', 'just', 'some', :args;
- ...
- method irc-custom-my-event ($just, $some, :$args) { }
-```
-
-No Message Object is involved in custom events.
diff --git a/historical-archive/DESIGN/specs-and-references.md b/historical-archive/DESIGN/specs-and-references.md
deleted file mode 100644
index 917e606..0000000
--- a/historical-archive/DESIGN/specs-and-references.md
+++ /dev/null
@@ -1,17 +0,0 @@
-
-# Specs
-
-* [Numerics and other awesome info](https://www.alien.net.au/irc/)
-* [RFC 1459](https://tools.ietf.org/html/rfc1459)
-* [RFC 2810](https://tools.ietf.org/html/rfc2810)
-* [RFC 2811](https://tools.ietf.org/html/rfc2811)
-* [RFC 2812](https://tools.ietf.org/html/rfc2812)
-* [RFC 2813](https://tools.ietf.org/html/rfc2813)
-* [WebIRC](https://irc.wiki/WebIRC)
-* [CTCP SPEC](http://cpansearch.perl.org/src/HINRIK/POE-Component-IRC-6.78/docs/ctcpspec.html)
-* [DCC Description](http://www.irchelp.org/irchelp/rfc/dccspec.html)
-* [DCC2](https://tools.ietf.org/id/draft-smith-irc-dcc2-negotiation-00.txt)
-
-# Future
-
-IRCv3 group: http://ircv3.net/
diff --git a/historical-archive/README.md b/historical-archive/README.md
deleted file mode 100644
index 2eed585..0000000
--- a/historical-archive/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-The documents in this directory are kept for historical reasons.
-
-Their contents do not represent what the module does or should do.
diff --git a/lib/IRC/Client.pm6 b/lib/IRC/Client.pm6
deleted file mode 100644
index bd0d861..0000000
--- a/lib/IRC/Client.pm6
+++ /dev/null
@@ -1,431 +0,0 @@
-unit class IRC::Client;
-
-use IO::Socket::Async::SSL;
-
-use IRC::Client::Message;
-use IRC::Client::Grammar;
-use IRC::Client::Server;
-use IRC::Client::Grammar::Actions;
-
-my class IRC_FLAG_NEXT {};
-
-role IRC::Client::Plugin is export {
- my IRC_FLAG_NEXT $.NEXT;
- has $.irc is rw;
-}
-
-has @.filters where .all ~~ Callable;
-has %.servers where .values.all ~~ IRC::Client::Server;
-has @.plugins;
-has $.debug;
-has Lock $!lock = Lock.new;
-has Channel $!event-pipe = Channel.new;
-has Channel $!socket-pipe = Channel.new;
-has Bool $.autoprefix = True;
-
-my &colored = (try require Terminal::ANSIColor) === Nil
- && sub (Str $s, $) { $s } ||
- ::('Terminal::ANSIColor::EXPORT::DEFAULT::&colored');
-BEGIN $! = Nil;
- $! = Nil; # don't serialize any exceptions from the above
-
-submethod BUILD (
- Int:D :$!debug = 0,
- :$filters = (),
- :$plugins = (),
- :$servers = {},
- Int:D :$port where 0 <= $_ <= 65535 = 6667,
- Str :$password,
- Str:D :$host = 'localhost',
- :$nick = ['P6Bot'],
- :$alias = [],
- Bool:D :$ssl = False,
- Str :$ca-file,
- Str:D :$username = 'Perl6IRC',
- Str:D :$userhost = 'localhost',
- Str:D :$userreal = 'Perl6 IRC Client',
- :$channels = ('#perl6',),
- Bool:D :$autoprefix = True,
-) {
- @!filters = @$filters;
- @!plugins = @$plugins;
- $!autoprefix = $autoprefix;
-
- my %servers = %$servers;
-
- my %all-conf = :$port, :$password, :$host, :$nick, :$alias,
- :$username, :$userhost, :$userreal, :$channels, :$ssl, :$ca-file;
-
- %servers = '_' => {} unless %servers;
- for %servers.keys -> $label {
- my $conf = %servers{$label};
- my $s = IRC::Client::Server.new(
- :socket(Nil),
- :$label,
- :channels( @($conf<channels> // %all-conf<channels>) ),
- :nick[ |($conf<nick> // %all-conf<nick>) ],
- :alias[ |($conf<alias> // %all-conf<alias>) ],
- |%(
- <host password port username userhost userreal ssl ca-file>
- .map: { $_ => $conf{$_} // %all-conf{$_} }
- ),
- );
- # Automatically add nick__ variants if given just one nick
- $s.nick[1..3] = "$s.nick()[0]_", "$s.nick()[0]__", "$s.nick()[0]___"
- if $s.nick.elems == 1;
- $s.current-nick = $s.nick[0];
- %!servers{$label} = $s;
- }
-}
-
-method join (*@channels, :$server) {
- self.send-cmd: 'JOIN', ($_ ~~ Pair ?? .kv !! .Str), :$server
- for @channels;
- self;
-}
-
-method nick (*@nicks, :$server = '*') {
- @nicks[1..3] = "@nicks[0]_", "@nicks[0]__", "@nicks[0]___" if @nicks == 1;
- self!set-server-attr($server, 'nick', @nicks);
- self!set-server-attr($server, 'current-nick', @nicks[0]);
- self.send-cmd: 'NICK', @nicks[0], :$server;
- self;
-}
-
-method part (*@channels, :$server) {
- self.send-cmd: 'PART', $_, :$server for @channels;
- self;
-}
-
-method quit (:$server = '*') {
- if $server eq '*' { .has-quit = True for %!servers.values; }
- else { self!get-server($server).has-quit = True; }
- self.send-cmd: 'QUIT', :$server;
- self;
-}
-
-method run {
- .irc = self for @.plugins.grep: { .DEFINITE and .^can: 'irc' };
- start {
- my $closed = $!event-pipe.closed;
- loop {
- if $!event-pipe.receive -> $e {
- $!debug and debug-print $e, :in, :server($e.server);
- $!lock.protect: {
- self!handle-event: $e;
- CATCH { default { warn $_; warn .backtrace } }
- };
- }
- elsif $closed { last }
- }
- CATCH { default { warn $_; warn .backtrace } }
- }
-
- .irc-started for self!plugs-that-can('irc-started');
- self!connect-socket: $_ for %!servers.values;
- loop {
- my $s = $!socket-pipe.receive;
- self!connect-socket: $s unless $s.has-quit;
- unless %!servers.values.grep({!.has-quit}) {
- $!debug and debug-print 'All servers quit by user. Exiting', :sys;
- last;
- }
- }
-}
-
-method send (:$where!, :$text!, :$server, :$notice) {
- for $server || |%!servers.keys.sort {
- if self!get-server($_).is-connected {
- self.send-cmd: $notice ?? 'NOTICE' !! 'PRIVMSG', $where, $text,
- :server($_);
- }
- else {
- $!debug and debug-print( :out, :server($_),
- '.send() called for an unconnected server. Skipping...'
- );
- }
- }
-
- self;
-}
-
-###############################################################################
-###############################################################################
-###############################################################################
-###############################################################################
-###############################################################################
-###############################################################################
-
-method !change-nick ($server) {
- my $idx = 0;
- for $server.nick.kv -> $i, $n {
- next unless $n eq $server.current-nick;
- $idx = $i + 1;
- $idx = 0 if $idx == $server.nick.elems;
- last;
- };
- if $idx == 0 {
- Promise.in(10).then: {
- $server.current-nick = $server.nick[$idx];
- self.send-cmd: "NICK $server.current-nick()", :$server;
- }
- }
- else {
- $server.current-nick = $server.nick[$idx];
- self.send-cmd: "NICK $server.current-nick()", :$server;
- }
-}
-
-method !connect-socket ($server) {
- $!debug and debug-print 'Attempting to connect to server', :out, :$server;
-
- my $socket;
-
- if ($server.ssl) {
- $socket = IO::Socket::Async::SSL.connect($server.host, $server.port, ca-file => $server.ca-file);
- } else {
- $socket = IO::Socket::Async.connect($server.host, $server.port);
- }
-
- $socket.then: sub ($prom) {
- if $prom.status ~~ Broken {
- $server.is-connected = False;
- $!debug and debug-print "Could not connect: $prom.cause()", :out, :$server;
- sleep 10;
- $!socket-pipe.send: $server;
- return;
- }
-
- $server.socket = $prom.result;
-
- self!ssay: "PASS $server.password()", :$server
- if $server.password.defined;
- self!ssay: "NICK {$server.nick[0]}", :$server;
-
- self!ssay: :$server, join ' ', 'USER', $server.username,
- $server.username, $server.host, ':' ~ $server.userreal;
-
- my $left-overs = '';
- react {
- whenever $server.socket.Supply :bin -> $buf is copy {
- my $str = try $buf.decode: 'utf8';
- $str or $str = $buf.decode: 'latin-1';
- $str = ($left-overs//'') ~ $str;
-
- (my $events, $left-overs) = self!parse: $str, :$server;
- $!event-pipe.send: $_ for $events.grep: *.defined;
-
- CATCH { default { warn $_; warn .backtrace } }
- }
- }
-
- unless $server.has-quit {
- $server.is-connected = False;
- $!debug and debug-print "Connection closed", :in, :$server;
- sleep 10;
- }
-
- $!socket-pipe.send: $server;
- CATCH { default { warn $_; warn .backtrace; } }
- }
-}
-
-method !handle-event ($e) {
- my $s = %!servers{ $e.server };
- given $e.command {
- when '001' {
- $s.current-nick = $e.args[0];
- self.join: $s.channels, :server($s);
- }
- when 'PING' { return $e.reply; }
- when '433'|'432' { self!change-nick: $s; }
- }
-
- my $event-name = 'irc-' ~ $e.^name.subst('IRC::Client::Message::', '')
- .lc.subst: '::', '-', :g;
-
- my @events = flat gather {
- given $event-name {
- when 'irc-privmsg-channel' | 'irc-notice-channel' {
- my $nick = $s.current-nick;
- my @aliases = $s.alias;
- if $e.text ~~ s/^ [ $nick | @aliases ] <[,:]> \s*// {
- take 'irc-addressed', ('irc-to-me' if $s.is-connected);
- }
- elsif $e.text ~~ / << [ $nick | @aliases ] >> /
- and $s.is-connected
- {
- take 'irc-mentioned';
- }
- take $event-name, $event-name eq 'irc-privmsg-channel'
- ?? 'irc-privmsg' !! 'irc-notice';
- }
- when 'irc-privmsg-me' {
- take $event-name, ('irc-to-me' if $s.is-connected),
- 'irc-privmsg';
- }
- when 'irc-notice-me' {
- take $event-name, ('irc-to-me' if $s.is-connected),
- 'irc-notice';
- }
- when 'irc-mode-channel' | 'irc-mode-me' {
- take $event-name, 'irc-mode';
- }
- when 'irc-numeric' {
- if $e.command eq '001' {
- $s.is-connected = True;
- take 'irc-connected';
- }
-
- # prefix numerics with 'n' as irc-\d+ isn't a valid identifier
- take 'irc-' ~ ('n' if $e ~~ IRC::Client::Message::Numeric)
- ~ $e.command, $event-name;
- }
- default { take $event-name }
- }
- take 'irc-all';
- }
-
- EVENT: for @events -> $event {
- debug-print "emitting `$event`", :sys
- if $!debug >= 3 or ($!debug == 2 and not $event eq 'irc-all');
-
- for self!plugs-that-can($event, $e) {
- my $res is default(Nil) = ."$event"($e);
- next if $res ~~ IRC_FLAG_NEXT;
-
- # Do not .reply with bogus return values
- last EVENT if $res ~~ IRC::Client | Supply | Channel;
-
- if $res ~~ Promise {
- $res.then: {
- $e.?reply: $^r.result
- unless $^r.result ~~ Nil or $e.?replied;
- }
- } else {
- $e.?reply: $res unless $res ~~ Nil or $e.?replied;
- }
- last EVENT;
-
- CATCH { default { warn $_, .backtrace; } }
- }
- }
-}
-
-method !parse (Str:D $str, :$server) {
- return |IRC::Client::Grammar.parse(
- $str,
- :actions( IRC::Client::Grammar::Actions.new: :irc(self), :$server )
- ).made;
-}
-
-method !plugs-that-can ($method, |c) {
- gather {
- for @!plugins -> $plug {
- take $plug if .cando: \($plug, |c)
- for $plug.^can: $method;
- }
- }
-}
-
-method !get-server ($server is copy) {
- $server //= '_'; # stupid Perl 6 and its sig defaults
- return $server if $server ~~ IRC::Client::Server;
- return %!servers{$server};
-}
-
-method send-cmd ($cmd, *@args is copy, :$prefix = '', :$server) {
- if $cmd eq 'NOTICE'|'PRIVMSG' {
- my ($where, $text) = @args;
- if @!filters
- and my @f = @!filters.grep({
- .signature.ACCEPTS: \($text)
- or .signature.ACCEPTS: \($text, :$where)
- })
- {
- start {
- CATCH { default { warn $_; warn .backtrace } }
- for @f -> $f {
- given $f.signature.params.elems {
- when 1 { $text = $f($text); }
- when 2 { ($text, $where) = $f($text, :$where); }
- }
- }
- self!ssay: :$server, join ' ', $cmd, $where, ":$prefix$text";
- }
- }
- else {
- self!ssay: :$server, join ' ', $cmd, $where, ":$prefix$text";
- }
- }
- else {
- if @args {
- my $last := @args[*-1];
- $last = ':' ~ $last
- if not $last or $last.starts-with: ':' or $last.match: /\s/;
- }
- self!ssay: :$server, join ' ', $cmd, @args;
- }
-}
-
-method !set-server-attr ($server, $method, $what) {
- if $server ne '*' {
- %!servers{$server}."$method"() = $what ~~ List ?? @$what !! $what;
- return;
- }
-
- for %!servers.values {
- ."$method"() = $what ~~ List ?? @$what !! $what ;
- }
-}
-
-method !ssay (Str:D $msg, :$server is copy) {
- $server //= '*';
- $!debug and debug-print $msg, :out, :$server;
- %!servers{$_}.socket.print: "$msg\n"
- for |($server eq '*' ?? %!servers.keys.sort !! ~$server);
- self;
-}
-
-###############################################################################
-###############################################################################
-###############################################################################
-###############################################################################
-###############################################################################
-###############################################################################
-
-sub debug-print ($str, :$in, :$out, :$sys, :$server) {
- my $server-str = $server
- ?? colored(~$server, 'bold white on_cyan') ~ ' ' !! '';
-
- my @bits = (
- $str ~~ IRC::Client::Message::Privmsg|IRC::Client::Message::Notice
- ?? ":$str.usermask() $str.command() $str.args()[]"
- !! $str.Str
- ).split: ' ';
-
- if $in {
- my ($pref, $cmd) = 0, 1;
- if @bits[0] eq '❚⚠❚' {
- @bits[0] = colored @bits[0], 'bold white on_red';
- $pref++; $cmd++;
- }
- @bits[$pref] = colored @bits[$pref], 'bold magenta';
- @bits[$cmd] = (@bits[$cmd]//'') ~~ /^ <[0..9]>**3 $/
- ?? colored(@bits[$cmd]//'', 'bold red')
- !! colored(@bits[$cmd]//'', 'bold yellow');
- put colored('▬▬▶ ', 'bold blue' ) ~ $server-str ~ @bits.join: ' ';
- }
- elsif $out {
- @bits[0] = colored @bits[0], 'bold magenta';
- put colored('◀▬▬ ', 'bold green') ~ $server-str ~ @bits.join: ' ';
- }
- elsif $sys {
- put colored(' ' x 4 ~ '↳', 'bold white') ~ ' '
- ~ @bits.join(' ')
- .subst: /(\`<-[`]>+\`)/, { colored(~$0, 'bold cyan') };
- }
- else {
- die "Unknown debug print mode";
- }
-}
diff --git a/lib/IRC/Client.rakumod b/lib/IRC/Client.rakumod
new file mode 100644
index 0000000..6df8de0
--- /dev/null
+++ b/lib/IRC/Client.rakumod
@@ -0,0 +1,332 @@
+#! /usr/bin/env false
+
+use v6.d;
+
+use Log;
+
+use IRC::Client::Core;
+use IRC::Client::Handler;
+use IRC::Client::Message;
+use IRC::Client::Plugin;
+
+#| A simple IRC client, intended for automating interactions.
+unit class IRC::Client;
+
+#| The host to connect to.
+has Str $.host = '127.0.0.1';
+
+#| The port to connect to.
+has Int $.port = 6697;
+
+#| Use SSL. Requires IO::Socket::Async::SSL to be installed.
+has Bool $.ssl = True;
+
+#| A list of channels to join on startup.
+has Str @.channels is rw;
+
+#| A list of acceptable nicknames to use.
+has Str @.nicks;
+
+#| The user to identify as.
+has Str $.user = 'raku';
+
+#| The real name to identify as.
+has Str $.real-name = 'IRC::Client';
+
+#| A list of plugins to use.
+has IRC::Client::Plugin @.plugins;
+
+#| The timeout between liveness checks (a PING to itself).
+has Real $.liveness-check-timeout = 30;
+
+#| The timeout between sending individual messages.
+has Real $.send-timeout = 0.2;
+
+#| The bot's own full prefix.
+has Str $.prefix is rw;
+
+#| The current nick of the bot.
+has Str $.nick is rw;
+
+#| Whether the bot is already connected.
+has Bool $.connected is rw;
+
+#| The connection with the IRC server.
+has $!connection;
+
+#| A supplier for incoming messages.
+has Supplier $!in;
+
+#| A Channel for outgoing messages.
+has Channel $!out;
+
+submethod TWEAK
+{
+ # Due to lack of knowledge on how to properly do "protected" variables
+ # in Raku, I'm just throwing a lot of warnings when you shouldn't be
+ # setting something.
+ if ($!connected) { .warning("Don't set connected on .new!") with $Log::instance }
+ if ($!prefix) { .warning("Don't set prefix on .new!") with $Log::instance }
+ if ($!nick) { .warning("Don't set nick on .new!") with $Log::instance }
+
+ $!connected = False;
+}
+
+#| Start the IRC client, connecting to the server and signing on to the
+#| network.
+method start
+{
+ # Sanity checks
+ if (!@!nicks) {
+ .error("You must specify at least one nickname") with $Log::instance;
+ return;
+ }
+
+ # Include the core functionality plugin
+ @!plugins.unshift(IRC::Client::Core.new);
+
+ # Insert IRC::Client object into plugins
+ for @!plugins.grep(* ~~ IRC::Client::Plugin:D) {
+ $_.irc = self;
+ }
+
+ # Set up suppliers
+ $!in = Supplier.new;
+ $!out = Channel.new;
+
+ # Set up a tap to handle incomding messages, outside of the react
+ # block. This should help in keeping the react block free of
+ # long-running code.
+ $!in.Supply.tap(sub ($message) {
+ try {
+ CATCH {
+ default {
+ my $exception = $_
+ .gist
+ .lines
+ .map(*.trim-trailing)
+ .join("\n")
+ ;
+
+ .critical($exception) with $Log::instance;
+ }
+ }
+
+ .debug("Handling $message") with $Log::instance;
+ IRC::Client::Handler.handle(IRC::Client::Message.new($message, self));
+ }
+ });
+
+ # Dispatch an irc-started event. This event should occurr once per run,
+ # to perform certain initial setups like an HTTP server.
+ IRC::Client::Handler.dispatch(['irc-started' => self], self);
+
+ # Pick the socket class
+ my $socket = !$!ssl ?? IO::Socket::Async !! do {
+ require ::('IO::Socket::Async::SSL');
+ };
+
+ # Connect to the server in a loop, to ensure automatic re-connection
+ # upon any issue.
+ loop {
+ .debug("Connecting to $!host:$!port") with $Log::instance;
+
+ try {
+ CATCH {
+ default {
+ my $exception = $_
+ .gist
+ .lines
+ .map(*.trim-trailing)
+ .join("\n")
+ ;
+
+ .emergency($exception) with $Log::instance;
+ }
+ }
+
+ $!connection = await $socket.connect($!host, $!port);
+ .debug("Connected to $!host:$!port") with $Log::instance;
+
+ # Create a buffer for incoming data
+ my $buffer;
+
+ react {
+ # Setup handling incoming messages
+ whenever $!connection.Supply -> $message {
+ for $message.comb -> $character {
+ given ($character) {
+ # When \r\n is encountered, it signifies the end of a message,
+ # so emit whatever is in the $!buffer to the $!in Supply.
+ when "\r\n" {
+ .info("< $buffer") with $Log::instance;
+ $!in.emit($buffer);
+ $buffer = '';
+ }
+
+ # Otherwise, add the character to the $!buffer.
+ default { $buffer ~= $character }
+ }
+ }
+ }
+
+ # Setup handling outgoing messages
+ whenever Supply.interval($!send-timeout) {
+ if ($!connected) {
+ with ($!out.poll) -> $message {
+ .notice("> $message") with $Log::instance;
+ $!connection.put($message);
+ }
+ }
+ }
+
+ # Simple keep-alive check, to ensure the socket
+ # is still open and usable.
+ whenever Supply.interval($!liveness-check-timeout).skip {
+ if ($!connected) {
+ self.privmsg($!nick, "PING {now.to-posix.first.subst('.', ' ')}", :ctcp);
+ }
+ }
+
+ # Setup handling ^c
+ whenever signal(SIGINT) {
+ .notice('Caught ^c') with $Log::instance;
+ self.stop('Caught ^c');
+ exit 0;
+ }
+
+ # Log on to the server
+ IRC::Client::Handler.dispatch(['irc-setup' => self], self);
+
+ LAST {
+ $!connected = False;
+ .error('Socket closed') with $Log::instance;
+ }
+ }
+ }
+
+ # Wait a small amount of time, in order to not blast an IRC
+ # server with connections when something is wrong.
+ sleep(5);
+ }
+}
+
+#| Stop the IRC client, sending a QUIT to the server and closing the
+#| connection.
+method stop (
+ Str:D $reason = ''
+) {
+ my $message = "QUIT :$reason";
+
+ .notice("> $message") with $Log::instance;
+
+ $!connected = False;
+ $!connection.put($message);
+ $!connection.close;
+}
+
+#
+# Backwards compatability
+#
+
+method run (
+) is DEPRECATED('IRC::Client.start') {
+ self.start()
+}
+
+method quit (
+) is DEPRECATED('IRC::Client.stop') {
+ self.stop()
+}
+
+method send (
+ :$where!,
+ :$text!,
+ :$notice
+) is DEPRECATED('IRC::Client.privmsg or IRC::Client.notice') {
+ self."{$notice ?? 'notice' !! 'privmsg'}"($where, $text)
+}
+
+#
+# Barebones messaging
+#
+
+#| Send a raw line to the IRC server.
+method send-raw (
+ Str:D $message,
+) {
+ $!out.send($message);
+}
+
+#
+# Convenience methods
+#
+
+method join (
+ Str:D $channel,
+) {
+ self.send-raw("JOIN $channel");
+}
+
+method set-nick (
+ Str:D $nick,
+) {
+ $!nick = $nick;
+ self.send-raw("NICK :$nick");
+}
+
+method part (
+ Str:D $channel,
+) {
+ self.send-raw("PART $channel");
+}
+
+multi method privmsg (
+ Str:D $target,
+ Str:D $message,
+ Bool :$ctcp where { !$_ },
+) {
+ self.send-raw("PRIVMSG $target :$message");
+}
+
+multi method privmsg (
+ Str:D $target,
+ Str:D $message,
+ Bool :$ctcp! where { $_ },
+) {
+ self.privmsg($target, "\x[1]$message\x[1]");
+}
+
+multi method notice (
+ Str:D $target,
+ Str:D $message,
+ Bool :$ctcp where { !$_ },
+) {
+ self.send-raw("NOTICE $target :$message");
+}
+
+multi method notice (
+ Str:D $target,
+ Str:D $message,
+ Bool :$ctcp where { $_ },
+) {
+ self.notice($target, "\x[1]$message\x[1]");
+}
+
+=begin pod
+
+=NAME IRC::Client
+=AUTHOR Patrick Spek <~tyil/raku-devel@lists.sr.ht>
+=VERSION 0.0.0
+
+=head1 Synopsis
+
+=head1 Description
+
+=head1 Examples
+
+=head1 See also
+
+=end pod
+
+# vim: ft=perl6 noet
diff --git a/lib/IRC/Client/Core.rakumod b/lib/IRC/Client/Core.rakumod
new file mode 100644
index 0000000..8cba445
--- /dev/null
+++ b/lib/IRC/Client/Core.rakumod
@@ -0,0 +1,155 @@
+#! /usr/bin/env false
+
+use v6.d;
+
+use IRC::Client::Handler;
+use IRC::Client::Plugin;
+use Log;
+
+#| A plugin for IRC::Client, encapsulating all the core functionality required
+#| of a functional IRC client.
+unit class IRC::Client::Core is IRC::Client::Plugin;
+
+#| Special cased method to setup the initial connection with IRC.
+multi method irc-setup ($irc) {
+ $irc.connected = True;
+ $irc.send-raw("USER {$irc.user} 8 * :{$irc.real-name}");
+ $irc.set-nick($irc.nicks[0]);
+}
+
+#| Special cased method to perform actions once the client is ready to go to
+#| work.
+multi method irc-ready ($irc) {
+ for $irc.channels -> $channel {
+ $irc.join($channel);
+ }
+}
+
+#| Handle RPL_WELCOME. This message indicates that the connection setup has
+#| been succesful.
+multi method irc-n001 ($message) {
+ my $nick = $message.params[0];
+
+ .debug("Logged in as $nick") with $Log::instance;
+
+ $.irc.prefix = $message[*-1].words.tail;
+ $.irc.nick = $nick;
+}
+
+#| Handle ERR_ERRONEUSNICKNAME. This message indicates that the nickname which
+#| is being used is not allowed, and the client should try an alternative
+#| instead.
+multi method irc-n432 ($message where { $_.params[0] eq '*' }) {
+ .error($message.params[*-1]) with $Log::instance;
+
+ my @nicks = $.irc.nicks;
+ my $index = @nicks.first($.irc.nick, :k) + 1 // 0;
+
+ if (@nicks.elems ≤ $index) {
+ .warning("No more nicks to try ({@nicks.join(' ')})") with $Log::instance;
+ $.irc.stop;
+ exit(3);
+ }
+
+ $.irc.set-nick(@nicks[$index]);
+}
+
+#| Handle ERR_NICKNAMEINUSE. This message indicates the nickname which is being
+#| used is already in use by another client. Another nickname should be tried
+#| instead.
+multi method irc-n433 ($message where { $_.params[0] eq '*' }) {
+ self.irc-n432($message);
+}
+
+multi method irc-n433 ($message where { $_.params[0] ne '*'}) {
+ $.irc.nick = $_.params[1];
+}
+
+#| Handle ERR_CHANNELISFULL. This message indicates a channel could not be
+#| joined due to a user count limitation (+l).
+multi method irc-n471 ($message) {
+ self!unregister-channel($.irc, $message.params[1]);
+}
+
+#| Handle ERR_INVITEONLYCHAN. This message indicates a channel could not be
+#| joined because you must be invited (+i).
+multi method irc-n473 ($message) {
+ self!unregister-channel($message.params[1]);
+}
+
+#| Handle ERR_BANNEDFROMCHAN. This message indicates a channel could not be
+#| joined due to an active ban (+b).
+multi method irc-n474 ($message) {
+ self!unregister-channel($message.params[1]);
+}
+
+#| Handle ERR_BADCHANNELKEY. This message indicates a channel could not be
+#| joined because it requires a key, or the given key was incorrect.
+multi method irc-n475 ($message) {
+ self!unregister-channel($message.params[1]);
+}
+
+#| Handle JOIN, but only if it pertains to ourselves. This method will keep the
+#| IRC::Client.channels list synced to the channels the bot is actually in.
+multi method irc-join ($message where { $_.nickname eq $message.irc.nick }) {
+ self!register-channel($message.params[0]);
+}
+
+#| Handle PART, but only if it pertains to ourselves. This method will keep the
+#| IRC::Client.channels list synced to the channels the bot is actually in.
+multi method irc-part ($message where { $_.nickname eq $message.irc.nick }) {
+ self!unregister-channel($message.params[0]);
+}
+
+#| Handle a CTCP PING request.
+multi method irc-privmsg ($message where { $_.ctcp && $_.params[*-1].words[0] eq 'PING'}) {
+ $.irc.notice($message.nickname, "PING {$message.params[*-1].words[1..*]}", :ctcp);
+}
+
+#| Handle a CTCP VERSION request.
+multi method irc-privmsg ($message where { $_.ctcp && $_.params[*-1].words[0] eq 'VERSION'}) {
+ $.irc.notice($message.nickname, 'VERSION raku/IRC::Client v0.1.0', :ctcp);
+}
+
+#| Handle a PING command. This message should be responded to with a PONG
+#| reply, to indicate the client is still alive.
+multi method irc-ping ($message) {
+ $.irc.send-raw("PONG :{$message.params[*-1]}");
+}
+
+#| Convenience method for adding a channel to the IRC::Client.channels list.
+method !register-channel ($channel) {
+ my @channels = $.irc.channels;
+
+ return if @channels ∋ $channel;
+
+ .debug("Adding $channel to the IRC::Client.channels") with $Log::instance;
+
+ $.irc.channels.append($channel);
+}
+
+#| Convenience method for removing a channel from the IRC::Client.channels
+#| list.
+method !unregister-channel ($channel) {
+ .debug("Removing $channel from IRC::Client.channels") with $Log::instance;
+
+ $.irc.channels = $.irc.channels.grep(* ne $channel);
+}
+
+=begin pod
+
+=NAME IRC::Client::Core
+=AUTHOR Patrick Spek <~tyil/raku-devel@lists.sr.ht>
+=VERSION 0.0.0
+
+=head1 Synopsis
+
+=head1 Description
+
+=head1 Examples
+
+=head1 See also
+
+=end pod
+
+# vim: ft=perl6 noet
diff --git a/lib/IRC/Client/Grammar.pm6 b/lib/IRC/Client/Grammar.pm6
deleted file mode 100644
index 9afecc7..0000000
--- a/lib/IRC/Client/Grammar.pm6
+++ /dev/null
@@ -1,26 +0,0 @@
-unit grammar IRC::Client::Grammar;
-token TOP { <message>+ <left-overs> }
-token left-overs { \N* }
-token SPACE { ' '+ }
-token message { [':' <prefix> <SPACE> ]? <command> <params> \n }
- regex prefix {
- [ <servername> || <nick> ['!' <user>]? ['@' <host>]? ]
- <before <SPACE>>
- }
- token servername { <host> }
- token nick {
- # the RFC grammar states nicks have to start with a letter,
- # however, modern server support and nick use disagrees with that
- # and nicks can start with special chars too
- [<letter> | <special>] [ <letter> | <number> | <special> ]*
- }
- token user { <-[\ \x[0]\r\n]>+? <before [<SPACE> | '@']>}
- token host { <-[\s!@]>+ }
- token command { <letter>+ | <number>**3 }
- token params { <SPACE>* [ ':' <trailing> | <middle> <params> ]? }
- token middle { <-[:\ \x[0]\r\n]> <-[\ \x[0]\r\n]>* }
- token trailing { <-[\x[0]\r\n]>* }
-
- token letter { <[a..zA..Z]> }
- token number { <[0..9]> }
- token special { <[-_\[\]\\`^{}|]> }
diff --git a/lib/IRC/Client/Grammar/Actions.pm6 b/lib/IRC/Client/Grammar/Actions.pm6
deleted file mode 100644
index b1fcc53..0000000
--- a/lib/IRC/Client/Grammar/Actions.pm6
+++ /dev/null
@@ -1,119 +0,0 @@
-unit class IRC::Client::Grammar::Actions;
-
-use IRC::Client::Message;
-
-has $.irc;
-has $.server;
-
-method TOP ($/) {
- $/.make: (
- $<message>».made,
- ~( $<left-overs> // '' ),
- );
-}
-
-method message ($match) {
- my %args;
- my $pref = $match<prefix>;
- for qw/nick user host/ {
- $pref{$_}.defined or next;
- %args<who>{$_} = ~$pref{$_};
- }
- %args<who><host> = ~$pref<servername> if $pref<servername>.defined;
-
- my $p = $match<params>;
- loop {
- %args<params>.append: ~$p<middle> if $p<middle>.defined;
-
- if ( $p<trailing>.defined ) {
- %args<params>.append: ~$p<trailing>;
- last;
- }
- last unless $p<params>.defined;
- $p = $p<params>;
- }
-
- my %msg-args =
- command => $match<command>.uc,
- args => %args<params>,
- host => %args<who><host>//'',
- irc => $!irc,
- nick => %args<who><nick>//'',
- server => $!server,
- usermask => ~($match<prefix>//''),
- username => %args<who><user>//'';
-
- my $msg;
- given %msg-args<command> {
- when /^ <[0..9]>**3 $/ {
- $msg = IRC::Client::Message::Numeric.new: |%msg-args;
- }
- when 'JOIN' {
- $msg = IRC::Client::Message::Join.new:
- :channel( %args<params>[0] ),
- |%msg-args;
- }
- when 'PART' {
- $msg = IRC::Client::Message::Part.new:
- :channel( %args<params>[0] ),
- |%msg-args;
- }
- when 'NICK' {
- $msg = IRC::Client::Message::Nick.new:
- :new-nick( %args<params>[0] ),
- |%msg-args;
- }
- when 'NOTICE' { $msg = msg-notice %args, %msg-args }
- when 'MODE' { $msg = msg-mode %args, %msg-args }
- when 'PING' { $msg = IRC::Client::Message::Ping.new: |%msg-args }
- when 'PRIVMSG' { $msg = msg-privmsg %args, %msg-args }
- when 'QUIT' { $msg = IRC::Client::Message::Quit.new: |%msg-args }
- default { $msg = IRC::Client::Message::Unknown.new: |%msg-args }
- }
-
- $match.make: $msg;
-}
-
-sub msg-privmsg (%args, %msg-args) {
- %args<params>[0] ~~ /^<[#&]>/
- and return IRC::Client::Message::Privmsg::Channel.new:
- :channel( %args<params>[0] ),
- :text( %args<params>[1] ),
- |%msg-args;
-
- return IRC::Client::Message::Privmsg::Me.new:
- :text( %args<params>[1] ),
- |%msg-args;
-}
-
-sub msg-notice (%args, %msg-args) {
- %args<params>[0] ~~ /^<[#&]>/
- and return IRC::Client::Message::Notice::Channel.new:
- :channel( %args<params>[0] ),
- :text( %args<params>[1] ),
- |%msg-args;
-
- return IRC::Client::Message::Notice::Me.new:
- :text( %args<params>[1] ),
- |%msg-args;
-}
-
-sub msg-mode (%args, %msg-args) {
- if %args<params>[0] ~~ /^<[#&]>/ {
- my @modes;
- for %args<params>[1..*-1].join.comb: /\S/ {
- state $sign;
- /<[+-]>/ and $sign = $_ and next;
- @modes.push: $sign => $_;
- };
- return IRC::Client::Message::Mode::Channel.new:
- :channel( %args<params>[0] ),
- :modes( @modes ),
- |%msg-args;
- }
- else {
- return IRC::Client::Message::Mode::Me.new:
- :modes( %args<params>[1..*-1].join.comb: /<[a..zA..Z]>/ ),
- |%msg-args;
- }
-}
diff --git a/lib/IRC/Client/Handler.rakumod b/lib/IRC/Client/Handler.rakumod
new file mode 100644
index 0000000..c966099
--- /dev/null
+++ b/lib/IRC/Client/Handler.rakumod
@@ -0,0 +1,174 @@
+#! /usr/bin/env false
+
+use v6.d;
+
+use IRC::Client::Message;
+use IRC::Client::Plugin;
+
+use Log;
+
+#| This class handles incoming messages, formats a context from them and
+#| dispatches it on to any registered plugin that can handle it.
+unit class IRC::Client::Handler;
+
+#| Handle numeric commands.
+multi method handle (
+ $event where { $_.command ~~ / \d ** 3/ },
+) {
+ my @events;
+
+ # Special cases for IRC::Client
+ @events.append('irc-connected' => $event.irc) if $event.command eq '001';
+ @events.append('irc-ready' => $event.irc) if $event.command eq '376';
+ @events.append('irc-ready' => $event.irc) if $event.command eq '422';
+
+ # Regular events, handles in the regular way
+ @events.append("irc-n{$event.command}" => $event);
+ @events.append('irc-numeric' => $event);
+ @events.append('irc-all' => $event);
+
+ self.dispatch(@events, $event.irc);
+}
+
+#| Handle PRIVMSG and NOTICE commands.
+multi method handle (
+ $event where { $_.command ∈ <PRIVMSG NOTICE> }
+) {
+ my @events;
+ my $stripped;
+
+ # If the message starts with the name of the client, it should dispatch
+ # an irc-addressed event.
+ if ($event.params[*-1] ~~ / ^ ( "{$event.irc.nick}" <[:;,]> <.ws> ) /) {
+ $stripped = $event.set-param(*-1, $event.params[*-1].substr($0.chars));
+ }
+
+
+ # A private message to the client should dispatch an irc-to-me event,
+ # optionally stripping away the client's current nickname from the
+ # start.
+ if ($event.params[0] eq $event.irc.nick) {
+ @events.append('irc-to-me' => $stripped // $event);
+ }
+
+ # A message to a public channel prefixed with the client's current
+ # nickname is both an irc-to-me event, and an irc-addressed event.
+ if ($event.params[0] ne $event.irc.nick && $stripped) {
+ @events.append('irc-to-me' => $stripped);
+ @events.append('irc-addressed' => $stripped);
+ }
+
+ @events.append("irc-{$event.command.fc}-me" => $event) if $event.params[0] eq $event.irc.nick;
+ @events.append('irc-mentioned' => $event) if $event.params[*-1].words ∋ $event.irc.nick;
+ @events.append("irc-{$event.command.fc}-channel" => $event) if $event.params[0] ~~ / ^ <[#&]> /;
+ @events.append("irc-{$event.command.fc}" => $event);
+ @events.append('irc-all' => $event);
+
+ self.dispatch(@events, $event.irc);
+}
+
+#| Handle MODE commands.
+multi method handle (
+ $event where { $_.command eq 'MODE' }
+) {
+ my @events;
+
+ @events.append('irc-mode-me' => $event) if $event.params[0] eq $event.irc.nick;
+ @events.append('irc-mode-channel' => $event) if $event.params[0] ~~ / ^ <[#&]> /;
+ @events.append('irc-mode' => $event);
+ @events.append('irc-all' => $event);
+
+ self.dispatch(@events, $event.irc);
+}
+
+#| Handle all IRC commands. Note that the order in which events are appended
+#| does matter, the most "narrow" event should always come first, getting
+#| broader as we go down the list.
+multi method handle (
+ $event,
+) {
+ my @events;
+
+ @events.append("irc-{$event.command.fc}" => $event);
+ @events.append('irc-all' => $event);
+
+ self.dispatch(@events, $event.irc);
+}
+
+#| Dispatch the message to all plugins.
+method dispatch (
+ @events,
+ $client, #= IRC::Client, but can't `use` this due to circular references
+) {
+ my @plugins = $client.plugins;
+
+ # Loop over all the plugins, and dispatch to each of them on a seperate
+ # thread. This allows plugins to take their sweet time, while not being
+ # in any other plugins' way.
+ for $client.plugins -> $plugin {
+ start {
+ CATCH {
+ default {
+ my $exception = $_;
+ .emergency($exception.gist.lines.map(*.trim-trailing).join("\n")) with $Log::instance;
+ }
+ }
+
+ self.dispatch-plugin(@events, $plugin)
+ }
+ }
+}
+
+#| Dispatch the message to all plugin methods that can handle them.
+method dispatch-plugin (
+ @events,
+ IRC::Client::Plugin $plugin,
+) {
+ for @events -> $event {
+ my $method = $event.key;
+ my $payload = $event.value;
+
+ # Check for available methods to handle this payload.
+ my @methods = $plugin.^can($method)
+ .map(*.candidates)
+ .flat
+ .grep(*.cando(\($plugin, $payload)))
+ ;
+
+ # Nothing to do if nothing was found.
+ next unless @methods;
+
+ .debug("Dispatching to {$plugin.^name}.$method") with $Log::instance;
+
+ my $response = $plugin."$method"($payload);
+
+ next unless $payload ~~ IRC::Client::Message;
+
+ # Depending on the return value of the method, something can be
+ # done.
+ given ($response) {
+ when Str {
+ $payload.reply($response);
+ last;
+ }
+ }
+ }
+}
+
+=begin pod
+
+=NAME IRC::Client::Handler
+=AUTHOR Patrick Spek <~tyil/raku-devel@lists.sr.ht>
+=VERSION 0.0.0
+
+=head1 Synopsis
+
+=head1 Description
+
+=head1 Examples
+
+=head1 See also
+
+=end pod
+
+# vim: ft=perl6 noet
diff --git a/lib/IRC/Client/Message.pm6 b/lib/IRC/Client/Message.pm6
deleted file mode 100644
index ff307ef..0000000
--- a/lib/IRC/Client/Message.pm6
+++ /dev/null
@@ -1,79 +0,0 @@
-unit package IRC::Client::Message;
-
-role IRC::Client::Message {
- has $.irc is required;
- has Str:D $.nick is required;
- has Str:D $.username is required;
- has Str:D $.host is required;
- has Str:D $.usermask is required;
- has Str:D $.command is required;
- has $.server is required;
- has $.args is required;
-
- method Str { ":$!usermask $!command $!args[]" }
-}
-
-constant M = IRC::Client::Message;
-
-role Join does M { has $.channel; }
-role Mode does M { has @.modes; }
-role Mode::Channel does Mode { has $.channel; }
-role Mode::Me does Mode { }
-role Nick does M { has $.new-nick; }
-role Numeric does M { }
-role Part does M { has $.channel; }
-role Quit does M { }
-role Unknown does M {
- method Str { "❚⚠❚ :$.usermask $.command $.args[]" }
-}
-
-role Ping does M {
- method reply { $.irc.send-cmd: 'PONG', $.args, :$.server; }
-}
-
-role Privmsg does M {
- has $.text is rw;
- has Bool $.replied is rw = False;
- method Str { $.text }
- method match ($v) { $.text ~~ $v }
-}
-role Privmsg::Channel does Privmsg {
- has $.channel;
- method reply ($text, :$where) {
- $.irc.autoprefix
- ?? $.irc.send-cmd: 'PRIVMSG', $where // $.channel, $text, :$.server, :prefix("$.nick, ")
- !! $.irc.send-cmd: 'PRIVMSG', $where // $.channel, $text, :$.server
- ;
- }
-}
-role Privmsg::Me does Privmsg {
- method reply ($text, :$where) {
- $.irc.send-cmd: 'PRIVMSG', $where // $.nick, $text,
- :$.server;
- }
-}
-
-role Notice does M {
- has $.text is rw;
- has Bool $.replied is rw = False;
- method Str { $.text }
- method match ($v) { $.text ~~ $v }
-}
-role Notice::Channel does Notice {
- has $.channel;
- method reply ($text, :$where) {
- $.irc.autoprefix
- ?? $.irc.send-cmd: 'NOTICE', $where // $.channel, $text, :$.server, :prefix("$.nick, ")
- !! $.irc.send-cmd: 'NOTICE', $where // $.channel, $text, :$.server
- ;
-
- $.replied = True;
- }
-}
-role Notice::Me does Notice {
- method reply ($text, :$where) {
- $.irc.send-cmd: 'NOTICE', $where // $.nick, $text,
- :$.server;
- $.replied = True;
- }
-}
diff --git a/lib/IRC/Client/Message.rakumod b/lib/IRC/Client/Message.rakumod
new file mode 100644
index 0000000..1e1ffb7
--- /dev/null
+++ b/lib/IRC/Client/Message.rakumod
@@ -0,0 +1,180 @@
+#! /usr/bin/env false
+
+use v6.d;
+
+use IRC::Grammar;
+
+#| A class to represent a message over IRC.
+unit class IRC::Client::Message;
+
+has Str $.servername;
+has Str $.nickname;
+has Str $.user;
+has Str $.host;
+has Str $.command;
+has Str @.params;
+has Bool $.ctcp = False;
+has $.irc;
+
+multi method new (
+ #| A string representing a line sent or received from an IRC server.
+ Str:D $line,
+
+ #| A reference the the IRC::Client handling the message.
+ $irc,
+) {
+ my $match = IRC::Grammar.parse($line);
+
+ # If the line doesn't match the IRC grammar, it's malformed and not up
+ # to IRC::Client to fix it.
+ die "Malformed message '$line'" if !?$match;
+
+ my $ctcp = False;
+ my @params = $match<params>.map(*.Str).Array;
+
+ my %prefix = $match<prefix>
+ .hash
+ .kv
+ .map(sub ($key, $value) { $key => $value.Str })
+ ;
+
+ if ($match<command> eq 'PRIVMSG'|'NOTICE') {
+ with ($match<params>.tail.Str) {
+ if ($_.comb[0, *-1].grep(* eq "\x[1]")) {
+ $ctcp = True;
+ @params[*-1] = $_.Str.substr(1, *-1);
+ } else {
+ @params[*-1] = $_.Str;
+ }
+ }
+ }
+
+ self.bless(
+ command => $match<command>.Str,
+ |%prefix,
+ :@params,
+ :$irc,
+ );
+}
+
+multi method new (
+ %params,
+) {
+ self.bless(|%params, params => %params<params>.list);
+}
+
+method gist
+{
+ @!params.tail
+}
+
+method prefix
+{
+ return $!servername if $!servername;
+
+ my $prefix = $!nickname;
+
+ if ($!host) {
+ if ($!user) {
+ $prefix = "$prefix!$!user";
+ }
+
+ $prefix = "$prefix@$!host";
+ }
+
+ $prefix;
+}
+
+method raku
+{
+ my $s = "$!command {@!params.join(' ')}";
+
+ with (self.prefix) { $s = ":$_ $s" }
+
+ given (@!params.elems) {
+ when 1 {
+ $s = "$s :{@!params[0]}"
+ }
+ default {
+ my @middle = @!params.head(*-1);
+ my $trailing = @!params.tail;
+
+ $s = "$s {@middle.join(' ')} :$trailing";
+ }
+ }
+
+
+ $s;
+}
+
+method words
+{
+ self.Str.words
+}
+
+method Hash
+{
+ %(
+ :$!servername,
+ :$!nickname,
+ :$!user,
+ :$!host,
+ :$!command,
+ :@!params,
+ :$!ctcp,
+ :$!irc,
+ )
+}
+
+method Str
+{
+ self.gist
+}
+
+#
+# Mutators
+#
+
+method set-param (
+ $index where { $_ ~~ Int:D|Code:D },
+ Str:D $value,
+) {
+ my %params = self.Hash;
+
+ %params<params>[$index] = $value;
+
+ self.new(|%params, params => %params<params>.list);
+}
+
+#
+# Convenience
+#
+
+method reply (
+ Str:D $message,
+) {
+ my $target = @!params[0] eq $!irc.nick ?? $!nickname !! @!params[0];
+
+ given ($!command) {
+ when 'PRIVMSG' { $!irc.privmsg($target, $message, :$!ctcp) }
+ when 'NOTICE' { $!irc.notice($target, $message, :$!ctcp) }
+ }
+}
+
+=begin pod
+
+=NAME IRC::Client::Message
+=AUTHOR Patrick Spek <~tyil/raku-devel@lists.sr.ht>
+=VERSION 0.0.0
+
+=head1 Synopsis
+
+=head1 Description
+
+=head1 Examples
+
+=head1 See also
+
+=end pod
+
+# vim: ft=perl6 noet
diff --git a/lib/IRC/Client/Plugin.rakumod b/lib/IRC/Client/Plugin.rakumod
new file mode 100644
index 0000000..7388416
--- /dev/null
+++ b/lib/IRC/Client/Plugin.rakumod
@@ -0,0 +1,28 @@
+#! /usr/bin/env false
+
+use v6.d;
+
+#| A base role for IRC::Client plugins. A plugin may handle any number of
+#| methods, in order to act upon events encountered by the client.
+unit role IRC::Client::Plugin;
+
+#| A reference to the IRC::Client the plugin is used by.
+has $.irc is rw;
+
+=begin pod
+
+=NAME IRC::Client::Plugin
+=AUTHOR Patrick Spek <~tyil/raku-devel@lists.sr.ht>
+=VERSION 0.0.0
+
+=head1 Synopsis
+
+=head1 Description
+
+=head1 Examples
+
+=head1 See also
+
+=end pod
+
+# vim: ft=perl6 noet
diff --git a/lib/IRC/Client/Server.pm6 b/lib/IRC/Client/Server.pm6
deleted file mode 100644
index 18f7755..0000000
--- a/lib/IRC/Client/Server.pm6
+++ /dev/null
@@ -1,20 +0,0 @@
-unit class IRC::Client::Server;
-
-has @.channels where .all ~~ Str|Pair;
-has @.nick where .all ~~ Str;
-has @.alias where .all ~~ Str|Regex;
-has Int $.port where 0 <= $_ <= 65535;
-has Bool $.ssl = False;
-has Str $.ca-file;
-has Str $.label;
-has Str $.host;
-has Str $.password;
-has Str $.username;
-has Str $.userhost;
-has Str $.userreal;
-has Str $.current-nick is rw;
-has Bool $.is-connected is rw;
-has Bool $.has-quit is rw;
-has $.socket is rw;
-
-method Str { $!label }
diff --git a/logotype/logo_32x32.png b/logotype/logo_32x32.png
deleted file mode 100644
index cf8dc8a..0000000
--- a/logotype/logo_32x32.png
+++ /dev/null
Binary files differ
diff --git a/t/00-use.t b/t/00-use.t
deleted file mode 100644
index 10cc37a..0000000
--- a/t/00-use.t
+++ /dev/null
@@ -1,11 +0,0 @@
-#!perl6
-
-use lib 'lib';
-use Test;
-
-use-ok 'IRC::Client';
-use-ok 'IRC::Client::Message';
-use-ok 'IRC::Client::Grammar';
-use-ok 'IRC::Client::Grammar::Actions';
-
-done-testing;
diff --git a/t/01-parser.t b/t/01-parser.t
new file mode 100644
index 0000000..56e4a77
--- /dev/null
+++ b/t/01-parser.t
@@ -0,0 +1,19 @@
+#!/usr/bin/env raku
+
+use v6.d;
+
+use Test;
+
+use IRC::Client;
+use IRC::Client::Message;
+
+my $client = IRC::Client.new;
+
+ok IRC::Client::Message.new(":irc.tyil.nl 432 * tyil_ircbot :Nickname too long, max. 9 characters", $client);
+ok IRC::Client::Message.new(":irc.tyil.nl 004 tyiltest irc.tyil.nl ngircd-26.1 abBcCFiIoqrRswx abehiIklmMnoOPqQrRstvVz", $client);
+
+ok IRC::Client::Message.new("PING :adams.freenode.net", $client);
+ok IRC::Client::Message.new(":freenode-connect!bot@freenode/utility-bot/frigg PRIVMSG tyil_ircbot :\x[1]VERSION\x[1]", $client);
+ok IRC::Client::Message.new(':tyil_ircbot!~raku@2a10:3781:a2:1:e8ed:81a8:92c9:b4aa JOIN #scriptkitties', $client);
+ok IRC::Client::Message.new(':freenode-connect!bot@freenode/utility-bot/frigg NOTICE tyil_ircbot :Welcome to freenode. To protect the network all new connections will be scanned for vulnerabilities. This will not harm your computer, and vulnerable hosts will be notified.', $client);
+
diff --git a/t/meta.t b/t/meta.t
deleted file mode 100644
index 6e2447d..0000000
--- a/t/meta.t
+++ /dev/null
@@ -1,7 +0,0 @@
-#!perl6
-
-use lib 'lib';
-use Test;
-use Test::META;
-meta-ok;
-done-testing;
diff --git a/t/release/01-basic.t b/t/release/01-basic.t
deleted file mode 100644
index fbbabda..0000000
--- a/t/release/01-basic.t
+++ /dev/null
@@ -1,63 +0,0 @@
-use lib <lib t/release>;
-use Test;
-use Test::When <release>;
-use Test::Notice;
-use IRC::Client;
-use Test::IRC::Server;
-
-my $Wait = (%*ENV<IRC_CLIENT_TEST_WAIT>//1) * 5;
-
-notice 'Testing connection to one server and joining two channels';
-
-diag 'Starting IRC Server';
-my $s = Test::IRC::Server.new;
-END { $s.kill };
-
-loop {
- last if $s.out.elems >= 2;
- sleep 0.5;
-}
-
-diag 'Starting IRC Client';
-start {
- my $irc = IRC::Client.new(
- :debug(%*ENV<IRC_CLIENT_DEBUG>//0)
- :nick<IRCBot>
- :channels<#perl6 #perl7>
- :servers(
- meow => { :port<5000> }
- )
- ).run;
-}
-
-diag 'Waiting for things to happen...';
-Promise.in($Wait).then: {$s.kill}
-await $s.promise;
-
-my $out = [
- {:args($[[Any],]), :event("ircd_registered")},
- {:args($[[5000, 1, "0.0.0.0"],]), :event("ircd_listener_add")},
- {
- :args(
- $[["IRCBot", 1, 'time', "+i", "~Perl6IRC",
- "simple.poco.server.irc", "simple.poco.server.irc",
- "Perl6 IRC Client"],]
- ),
- :event("ircd_daemon_nick")},
- {
- :args($[["IRCBot!~Perl6IRC\@simple.poco.server.irc", "#perl6"],]), :event("ircd_daemon_join")
- },
- {
- :args($[["IRCBot!~Perl6IRC\@simple.poco.server.irc", "#perl7"],]), :event("ircd_daemon_join")
- }
-];
-
-# Fix time signature;
-for $s.out {
- next unless .<event> eq 'ircd_daemon_nick';
- .<args>[0][2] = 'time';
-}
-
-is-deeply $s.out, $out, 'Server output looks right';
-
-done-testing;
diff --git a/t/release/02-multi-server.t b/t/release/02-multi-server.t
deleted file mode 100644
index 6a223d7..0000000
--- a/t/release/02-multi-server.t
+++ /dev/null
@@ -1,76 +0,0 @@
-use lib <lib t/release>;
-use Test;
-use Test::When <release>;
-use Test::Notice;
-use IRC::Client;
-use Test::IRC::Server;
-
-my $Wait = (%*ENV<IRC_CLIENT_TEST_WAIT>//1) * 5;
-
-notice 'Testing connection to four servers and joining two channels in each';
-
-diag 'Starting IRC Servers';
-my $s1 = Test::IRC::Server.new: :port<5020>;
-my $s2 = Test::IRC::Server.new: :port<5021>;
-my $s3 = Test::IRC::Server.new: :port<5022>;
-my $s4 = Test::IRC::Server.new: :port<5023>;
-END { $s1.kill; $s2.kill; $s3.kill; $s4.kill; };
-
-loop {
- last if $s1.out.elems & $s2.out.elems & $s3.out.elems & $s4.out.elems >= 2;
- sleep 0.5;
-}
-
-diag 'Starting IRC Client';
-start {
- my $irc = IRC::Client.new(
- :debug(%*ENV<IRC_CLIENT_DEBUG>//0)
- :nick<IRCBot>
- :channels<#perl6 #perl7>
- :servers(
- s1 => { :port<5020> },
- s2 => { :port<5021>, :nick<OtherBot>, :channels<#perl7 #perl9> },
- s3 => { :port<5022>, :channels<#perl10 #perl11> },
- s4 => { :port<5023>, :nick<YetAnotherBot> },
- )
- ).run;
-}
-
-diag 'Waiting for things to happen...';
-Promise.in($Wait).then: { $s1.kill; $s2.kill; $s3.kill; $s4.kill; }
-await Promise.allof: ($s1, $s2, $s3, $s4).map: *.promise;
-
-dd $s1.out;
-diag '----';
-dd $s2.out;
-diag '----';
-dd $s3.out;
-diag '----';
-dd $s4.out;
-diag '----';
-
-#
-# my $out = [
-# {:args($[[Any],]), :event("ircd_registered")},
-# {:args($[[5000, 1, "0.0.0.0"],]), :event("ircd_listener_add")},
-# {
-# :args(
-# $[["IRCBot", 1, 'time', "+i", "~Perl6IRC",
-# "simple.poco.server.irc", "simple.poco.server.irc",
-# "Perl6 IRC Client"],]
-# ),
-# :event("ircd_daemon_nick")},
-# {
-# :args($[["IRCBot!~Perl6IRC\@simple.poco.server.irc", "#perl6"],]), :event("ircd_daemon_join")
-# }
-# ];
-#
-# # Fix time signature;
-# for $s.out {
-# next unless .<event> eq 'ircd_daemon_nick';
-# .<args>[0][2] = 'time';
-# }
-#
-# is-deeply $s.out, $out, 'Server output looks right';
-
-done-testing;
diff --git a/t/release/Test/IRC/Server.pm6 b/t/release/Test/IRC/Server.pm6
deleted file mode 100644
index fed3d66..0000000
--- a/t/release/Test/IRC/Server.pm6
+++ /dev/null
@@ -1,20 +0,0 @@
-unit class Test::IRC::Server;
-
-use JSON::Fast;
-
-has $!port;
-has $!proc;
-has Promise $.promise;
-has @.out;
-
-submethod BUILD (:$!port = 5000, :$server = 't/release/servers/01-basic.pl') {
- $!proc = Proc::Async.new: 'perl', $server, $!port;
- $!proc.stdout.tap: {
- %*ENV<IRC_CLIENT_DEBUG> and dd .lines;
- @!out.append: |.lines».&from-json
- };
- $!proc.stderr.tap: { warn $_ };
- $!promise = $!proc.start;
-}
-
-method kill { $!proc.kill; }
diff --git a/t/release/servers/01-basic.pl b/t/release/servers/01-basic.pl
deleted file mode 100644
index 086e213..0000000
--- a/t/release/servers/01-basic.pl
+++ /dev/null
@@ -1,62 +0,0 @@
-use strict;
-use warnings;
-use JSON::Meth;
-use 5.020;
-use POE qw(Component::Server::IRC);
-
-$|++;
-
-my ($Port) = @ARGV;
-
-my %config = (
- servername => 'simple.poco.server.irc',
- nicklen => 15,
- network => 'SimpleNET'
-);
-
-my $pocosi = POE::Component::Server::IRC->spawn( config => \%config );
-
-POE::Session->create(
- package_states => [
- 'main' => [qw(_start _default)],
- ],
- heap => { ircd => $pocosi },
-);
-
-$poe_kernel->run();
-
-sub _start {
- my ($kernel, $heap) = @_[KERNEL, HEAP];
-
- $heap->{ircd}->yield('register', 'all');
- $heap->{ircd}->add_auth(mask => '*@*');
- $heap->{ircd}->add_listener(port => $Port);
- $heap->{ircd}->add_operator({
- username => 'moo',
- password => 'fishdont',
- });
-}
-
-sub _default {
- my ($event, @args) = @_[ARG0 .. $#_];
- say {
- event => $event,
- args => \@args,
- }->$j;
-
-
- # print "$event: ";
- # for my $arg (@args) {
- # if (ref($arg) eq 'ARRAY') {
- # print "[", join ( ", ", @$arg ), "] ";
- # }
- # elsif (ref($arg) eq 'HASH') {
- # print "{", join ( ", ", %$arg ), "} ";
- # }
- # else {
- # print "'$arg' ";
- # }
- # }
- #
- # print "\n";
- }