/
Bot.pm
161 lines (133 loc) · 3.7 KB
/
Bot.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use v6;
use Net::IRC::DefaultHandlers;
use Net::IRC::Parser;
use Net::IRC::Event;
class Net::IRC::Bot {
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
has $nick = "Rakudobot";
has @altnicks = $nick X~ ("_","__",^10);
has $username = "Clunky";
has $realname = '$@%# yeah, perl 6!';
has $server = "irc.perl.org";
has $port = 6667;
has $password;
has @channels = [];
#Most important part of the bot.
has @modules;
#Options
has $debug = False;
#State variables.
#TODO: Make this an object for cleaner syntax.
has %state;
submethod BUILD {
callsame;
@modules.push(Net::IRC::DefaultHandlers.new);
}
method !resetstate() {
%state = (
nick => $nick,
altnicks => @altnicks,
autojoin => @channels,
channels => %(),
loggedin => False,
connected => False,
)
}
method !connect(){
#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")}
};
say 'conn made';
#Send PASS if needed
$conn.sendln("PASS $password") 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");
say 'sent thing to srvr';
%state<connected> = True;
}
method !disconnect($quitmsg = "Leaving"){
if %state<connected> {
$conn.sendln("QUIT :$quitmsg");
$conn.close;
}
}
method run() {
self!disconnect;
self!connect;
loop {
#XXX: Support for timed events?
my $line = $conn.get
or die "Connection error.";
my $event = Net::IRC::Parser::RawEvent.parse($line)
or $*ERR.say("Could not parse the following IRC event: $line") and next;
say ~$event if $debug;
self!dispatch($event);
}
}
method !dispatch($raw) {
#Make an event object and fill it as much as we can.
#XXX: Should I just use a single cached Event to save memory?
my $who = ($raw<user> || $raw<server>);
$who does role { method Str { self<nick> || self<host> } };
#XXX Stupid workaround.
my $l = $raw<params>.elems;
my $event = Net::IRC::Event.new(
:raw($raw),
:command(~$raw<command>),
:conn($conn),
:state(%state),
:who($who),
:where(~$raw<params>[0]),
:what(~$raw<params>[$l ?? $l-1 !! 0]),
);
# Dispatch to the raw event handlers.
@modules>>.*"irc_{ lc $event.command }"($event);
given uc $event.command {
when "PRIVMSG" {
#Check to see if its a CTCP request.
if $event.what ~~ /^\c01 (.*) \c01$/ {
my $text = ~$0;
if $debug {
say "Received CTCP $text from {$event.who}" ~
( $event.where eq $event.who ?? '.' !! " (to channel {$event.where})." );
}
$text ~~ /^ (.+?) [<.ws> (.*)]? $/;
$event.what = $1 && ~$1;
@modules>>.*"ctcp_{ lc $0 }"($event);
#If its a CTCP ACTION then we also call 'emoted'
@modules>>.*emoted($event) if uc $0 eq 'ACTION';
}
else {
@modules>>.*said($event);
}
}
when "NOTICE" {
@modules>>.*noticed($event);
}
when "KICK" {
$event.what = $raw<params>[1];
@modules>>.*kicked($event);
}
when "JOIN" {
@modules>>.*joined($event);
}
when "NICK" {
@modules>>.*nickchange($event);
}
when "376"|"422" {
#End of motd / no motd. (Usually) The last thing a server sends the client on connect.
@modules>>.*connected($event)
}
}
}
}