Skip to content

Commit

Permalink
Merge pull request #6 from japhb/master
Browse files Browse the repository at this point in the history
Debugging, refactoring, and some new functionality (including a CommandHandler role and the seen command)
  • Loading branch information
Timbus committed Dec 27, 2013
2 parents 65d1058 + cf8d1b4 commit 6ff9359
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 53 deletions.
2 changes: 2 additions & 0 deletions Freenodebot.pl
Expand Up @@ -9,6 +9,8 @@
server => 'irc.freenode.org',
channels => <#bottest>,
modules => (
Net::IRC::Modules::ACME::Bark::LikeADog.new,
Net::IRC::Modules::ACME::Bark::LikeATree.new(prefix => '@'),
Net::IRC::Modules::ACME::Eightball.new,
#Net::IRC::Modules::ACME::Unsmith.new
),
Expand Down
47 changes: 28 additions & 19 deletions lib/Net/IRC/Bot.pm
Expand Up @@ -4,11 +4,11 @@ use Net::IRC::Parser;
use Net::IRC::Event;

class Net::IRC::Bot {
has $conn is rw;
has $.conn is rw;

#Set some sensible defaults for the bot.
#These are not stored as state, they are just used for the bot's "start state"
#Changing things like $nick and @channels are tracked in %state
#Changing things like $nick and @channels are tracked in %.state
has $.nick = "Rakudobot";
has @.altnicks = $!nick X~ ("_","__",^10);
has $.username = "Clunky";
Expand All @@ -27,7 +27,7 @@ class Net::IRC::Bot {

#State variables.
#TODO: Make this an object for cleaner syntax.
has %state is rw;
has %.state is rw;

method new(|) {
my $obj = callsame();
Expand All @@ -36,7 +36,7 @@ class Net::IRC::Bot {
}

method !resetstate() {
%state = (
%.state = (
nick => $.nick,
altnicks => @.altnicks,
autojoin => @.channels,
Expand All @@ -50,26 +50,36 @@ class Net::IRC::Bot {
#Establish connection to server
self!resetstate;
say "Connecting to $.server on port $.port";
$conn = IO::Socket::INET.new(host => $.server, port => $.port)
but role {
method sendln(Str $string){self.send($string~"\c13\c10")}
};
my role irc-connection[$debug] {
method sendln(Str $string, :$scrubbed = $string){
say "»»» $scrubbed" if $debug;
self.send($string~"\c13\c10");
}
method get(|){
my $line = callsame();
say "<-- $line" if $debug;
$line;
}
}
$.conn = IO::Socket::INET.new(host => $.server, port => $.port)
but irc-connection[$.debug];

#Send PASS if needed
$conn.sendln("PASS $.password") if $.password;
$.conn.sendln("PASS $.password", scrubbed => 'PASS ...')
if $.password;

#Send NICK & USER.
#If the nick collides, we'll resend a new one when we recieve the error later.
#USER Parameters: <username> <hostname> <servername> <realname>
$conn.sendln("NICK $.nick");
$conn.sendln("USER $.username abc.xyz.net $.server :$.realname");
%state<connected> = True;
$.conn.sendln("NICK $.nick");
$.conn.sendln("USER $.username abc.xyz.net $.server :$.realname");
%.state<connected> = True;
}

method !disconnect($quitmsg = "Leaving"){
if %state<connected> {
$conn.sendln("QUIT :$quitmsg");
$conn.close;
if %.state<connected> {
$.conn.sendln("QUIT :$quitmsg");
$.conn.close;
}
}

Expand All @@ -79,14 +89,13 @@ class Net::IRC::Bot {
self!connect;
loop {
#XXX: Support for timed events?
my $line = $conn.get
my $line = $.conn.get
or die "Connection error.";
$line ~~ s/<[\n\r]>+$//;

my $event = Net::IRC::Parser::RawEvent.parse($line)
or $*ERR.say("Could not parse the following IRC event: $line.perl()") and next;

say ~$event if $.debug;
self!dispatch($event);
}
}
Expand All @@ -103,8 +112,8 @@ class Net::IRC::Bot {
my $event = Net::IRC::Event.new(
:raw($raw),
:command(~$raw<command>),
:conn($conn),
:state(%state),
:conn($.conn),
:state(%.state),
:who($who),
:where(~$raw<params>[0]),
:what(~$raw<params>[$l ?? $l-1 !! 0]),
Expand Down
48 changes: 48 additions & 0 deletions lib/Net/IRC/CommandHandler.pm
@@ -0,0 +1,48 @@
use v6;

enum RequiredIntro is export <
NONE
NICK
PREFIX
EITHER
BOTH
>;

role Net::IRC::CommandHandler {
has Str $.prefix is rw = '!';
has RequiredIntro $.required-intro is rw = EITHER;

method recognized($handler: $ev) {
return $ev.cache<CommandHandler>{$handler.prefix} //= (gather {
$ev.what ~~ token {
# Intro
^
[ \s* $<nick>=("$ev.state()<nick>") [ <[':' ',']> | \s ] ]? \s*
$<prefix>=("$handler.prefix()"?) \s*

# Actual command (and optional params)
$<command>=(\w+) [ <?> | \s+ $<params>=(.*) ]
$
} or take False;

given $.required-intro {
when NICK { take False unless $<nick> }
when PREFIX { take False unless $<prefix> }
when EITHER { take False unless $<prefix> || $<nick> }
when BOTH { take False unless $<prefix> && $<nick> }
}

take $/;
})[0];
}

multi method said ($ev where { $/ := $.recognized($ev) }) {
self.*"command_$<command>"($ev, $/);
}

method usage($ev, $usage) {
$ev.msg("Usage: $usage");
}
}

# vim: ft=perl6 tabstop=4 shiftwidth=4
3 changes: 3 additions & 0 deletions lib/Net/IRC/Event.pm
Expand Up @@ -13,6 +13,9 @@ class Net::IRC::Event {
has $.what is rw;
has $.where is rw;

# Per-event cache to prevent many modules from repeating the same work
has %.cache;


##Utility methods
method msg($text, $to = $.where) {
Expand Down
14 changes: 14 additions & 0 deletions lib/Net/IRC/Modules/ACME.pm
@@ -1,4 +1,6 @@
use v6;
use Net::IRC::CommandHandler;

module Net::IRC::Modules::ACME;

class Net::IRC::Modules::ACME::Eightball {
Expand Down Expand Up @@ -30,5 +32,17 @@ class Net::IRC::Modules::ACME::Unsmith {
}
}

class Net::IRC::Modules::ACME::Bark::LikeADog does Net::IRC::CommandHandler {
method command_bark($ev, $match) {
$ev.msg("Woof!");
}
}

class Net::IRC::Modules::ACME::Bark::LikeATree does Net::IRC::CommandHandler {
method command_bark($ev, $match) {
$ev.msg("The bark is smooth and brown.");
}
}

# vim: ft=perl6 tabstop=4 shiftwidth=4

2 changes: 1 addition & 1 deletion lib/Net/IRC/Modules/Autoident.pm
Expand Up @@ -5,7 +5,7 @@ class Net::IRC::Modules::Autoident {
has $.password = die "Need to tell Autoident your password if you want it to work!";
multi method connected($ev) {
say "Identifying with nickserv..";
$ev.conn.sendln("NS IDENTIFY $.password");
$ev.conn.sendln("NS IDENTIFY $.password", scrubbed => 'NS IDENTIFY ...');
}
}

Expand Down
58 changes: 58 additions & 0 deletions lib/Net/IRC/Modules/Seen.pm
@@ -0,0 +1,58 @@
use v6;
use Net::IRC::CommandHandler;
use Net::IRC::TextUtil;

class Net::IRC::Modules::Seen does Net::IRC::CommandHandler {
class Seen {
has $.when = now;
has $.what;
has $.how;
}
has %!seen;

multi method said ( $ev ) {
%!seen{~$ev.who} := Seen.new(:how('saying:'), :what(~$ev.what));
}

multi method emoted ( $ev ) {
%!seen{~$ev.who} := Seen.new(:how("emoting: * $ev.who()"), :what(~$ev.what));
}

multi method kicked ( $ev ) {
%!seen{~$ev.what} := Seen.new(:how('being kicked from'), :what(~$ev.where));
}

multi method joined ( $ev ) {
%!seen{~$ev.who} := Seen.new(:how('joining'), :what(~$ev.where));
}

multi method nickchange ( $ev ) {
%!seen{~$ev.who} := Seen.new(:how('changing nick to'), :what(~$ev.what));
%!seen{~$ev.what} := Seen.new(:how('changing nick from'), :what(~$ev.who));
}


method command_seen ( $ev, $/ ) {
my @params = ($<params> // '').comb(/\S+/);
if @params {
for @params -> $nick {
if %!seen{$nick} -> $seen {
my $dt = DateTime.new($seen.when);
my $stamp = $dt.Str.subst('T', ' ');
my $seconds = now - $seen.when;
my $elapsed = friendly-duration($seconds);

$ev.msg("$nick was last seen at $stamp ($elapsed ago) $seen.how() $seen.what()");
}
else {
$ev.msg("I haven't seen $nick.");
}
}
}
else {
self.usage($ev, 'seen <nick> [<nick> ...]');
}
}
}

# vim: ft=perl6 tabstop=4 shiftwidth=4
43 changes: 10 additions & 33 deletions lib/Net/IRC/Modules/Tell.pm
@@ -1,15 +1,18 @@
use v6;
class Net::IRC::Modules::Tell {
use Net::IRC::CommandHandler;
use Net::IRC::TextUtil;

class Net::IRC::Modules::Tell does Net::IRC::CommandHandler {
class Message {
has $.sender;
has $.message;
has $.when;
}
has %messages;

multi method said ( $ev where {$ev.what ~~ /^<{$ev.state<nick>}><.punct>?<.ws>'tell'/} ) {
method command_tell ( $ev, $match ) {
my $from = $ev.who;
if $ev.what ~~ /tell <.ws> $<name>=<-space -punct>+ <.punct>? <.ws> $<msg>=[.+]/ {
if $match<params> ~~ /$<name>=<+ alpha + [ \[..\] \{..\} ]>+ <.punct>? <.ws> $<msg>=[.+]/ {
if $<name>.lc eq $from.lc|'me' {
$ev.msg("$from: I think you can tell yourself that!");
return;
Expand All @@ -20,6 +23,9 @@ class Net::IRC::Modules::Tell {
);
$ev.msg("$from: Noted. I'll pass that on when I see $<name>");
}
else {
self.usage($ev, 'tell <nick> <message>');
}
}

multi method said ( $ev where {$ev.who.lc ~~ %messages} ) {
Expand All @@ -38,40 +44,11 @@ class Net::IRC::Modules::Tell {
method !deliver-message( $ev ){
my $reciever = $ev.who;
for @(%messages{$reciever.lc}) -> $msg {
my $elapsed = self!format-time(time - $msg.when);
my $elapsed = friendly-duration(time - $msg.when);
$ev.msg("$reciever: <{$msg.sender}> {$msg.message} ::$elapsed ago");
}
%messages{$reciever.lc} = [];
}

method !format-time($elapsed) {
given $elapsed {
when * < 60 {
return "$elapsed second"~($elapsed != 1 ?? 's' !! '');
}
when * < 3570 {
my $mins = ($elapsed / 60).round;
return "$mins minute"~($mins != 1 ?? 's' !! '');
}
when * < 84600 {
my $hours = ($elapsed / 60 / 60).round;
return "$hours hour"~($hours != 1 ?? 's' !! '');
}
when * < 604800 {
my $days = ($elapsed / 60 / 60 / 24).round;
my $hours = ($elapsed % 86400 / 60 / 60).round;
return
"$days day" ~
($days != 1 ?? 's' !! '') ~
$hours ?? (", $hours hour" ~
($hours != 1 ?? 's' !! '') ) !! '';
}
default {
my $days = ($elapsed / 60 / 60 / 24).round;
return "$days day"~($days != 1 ?? 's' !! '');
}
}
}
}

# vim: ft=perl6 tabstop=4 shiftwidth=4
Expand Down
38 changes: 38 additions & 0 deletions lib/Net/IRC/TextUtil.pm
@@ -0,0 +1,38 @@
use v6;

module Net::IRC::TextUtil;

sub _s($num, $plural = 's', $singular = '') is export {
$num == 1 ?? $singular !! $plural;
}

sub friendly-duration($seconds) is export {
my $minute = 60;
my $hour = $minute * 60;
my $day = $hour * 24;
my $week = $day * 7;

given $seconds.Int {
when * < $minute {
"$_ second{_s($_)}"
}
when * < 59.5 * $minute {
my $minutes = ($_ / $minute).round;
"$minutes minute{_s($minutes)}"
}
when * < 23.5 * $hour {
my $hours = ($_ / $hour).round;
"$hours hour{_s($hours)}"
}
when * < 6.5 * $day {
my $days = ($_ / $day).round;
"$days day{_s($days)}"
}
default {
my $weeks = ($_ / $week).round;
"$weeks week{_s($weeks)}"
}
}
}

# vim: ft=perl6 tabstop=4 shiftwidth=4

0 comments on commit 6ff9359

Please sign in to comment.