diff options
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 @@ -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. - @@ -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 Binary files differdeleted file mode 100644 index cf8dc8a..0000000 --- a/logotype/logo_32x32.png +++ /dev/null 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"; - } |