line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Convos::Core::Connection; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Convos::Core::Connection - Represents a connection to an IRC server |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 SYNOPSIS |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
use Convos::Core::Connection; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
$c = Convos::Core::Connection->new( |
12
|
|
|
|
|
|
|
name => 'magnet', |
13
|
|
|
|
|
|
|
login => 'username', |
14
|
|
|
|
|
|
|
redis => Mojo::Redis->new, |
15
|
|
|
|
|
|
|
); |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
$c->connect; |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
Mojo::IOLoop->start; |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=head1 DESCRIPTION |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
This module use L to up a connection to an IRC server. The |
24
|
|
|
|
|
|
|
attributes used to do so is figured out from a redis server. |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
There are quite a few L that this module use: |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
=over 4 |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
=item * L events |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
L. |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=item * L events |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
L, L, L, |
37
|
|
|
|
|
|
|
L, L and L. |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=item * Other events |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
L, L, L, L, |
42
|
|
|
|
|
|
|
L, L, L, L |
43
|
|
|
|
|
|
|
L, l and L. |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=back |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
=cut |
48
|
|
|
|
|
|
|
|
49
|
36
|
|
|
36
|
|
154
|
use Mojo::Base 'Mojo::EventEmitter'; |
|
36
|
|
|
|
|
271
|
|
|
36
|
|
|
|
|
184
|
|
50
|
36
|
|
|
36
|
|
21595
|
use Mojo::IRC; |
|
36
|
|
|
|
|
549573
|
|
|
36
|
|
|
|
|
443
|
|
51
|
36
|
|
|
36
|
|
1226
|
use Mojo::JSON 'j'; |
|
36
|
|
|
|
|
60
|
|
|
36
|
|
|
|
|
1809
|
|
52
|
36
|
|
|
36
|
|
158
|
no warnings 'utf8'; |
|
36
|
|
|
|
|
57
|
|
|
36
|
|
|
|
|
1002
|
|
53
|
36
|
|
|
36
|
|
142
|
use IRC::Utils; |
|
36
|
|
|
|
|
48
|
|
|
36
|
|
|
|
|
1093
|
|
54
|
36
|
|
|
36
|
|
201
|
use Parse::IRC (); |
|
36
|
|
|
|
|
59
|
|
|
36
|
|
|
|
|
569
|
|
55
|
36
|
|
|
36
|
|
153
|
use Scalar::Util (); |
|
36
|
|
|
|
|
69
|
|
|
36
|
|
|
|
|
620
|
|
56
|
36
|
|
|
36
|
|
156
|
use Time::HiRes 'time'; |
|
36
|
|
|
|
|
53
|
|
|
36
|
|
|
|
|
326
|
|
57
|
36
|
|
|
36
|
|
18874
|
use Convos::Core::Util qw( as_id id_as ); |
|
36
|
|
|
|
|
89
|
|
|
36
|
|
|
|
|
2313
|
|
58
|
36
|
|
|
36
|
|
16875
|
use Sys::Hostname (); |
|
36
|
|
|
|
|
31377
|
|
|
36
|
|
|
|
|
843
|
|
59
|
36
|
|
|
36
|
|
179
|
use constant CHANNEL_LIST_CACHE_TIMEOUT => 3600; # TODO: Figure out how long to cache channel list |
|
36
|
|
|
|
|
56
|
|
|
36
|
|
|
|
|
2314
|
|
60
|
36
|
50
|
|
36
|
|
152
|
use constant DEBUG => $ENV{CONVOS_DEBUG} ? 1 : 0; |
|
36
|
|
|
|
|
49
|
|
|
36
|
|
|
|
|
217675
|
|
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head2 name |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
Name of the connection. Example: "freenode", "magnet" or "efnet". |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=head2 log |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Holds a L object. |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=head2 login |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
The username of the owner. |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
=head2 redis |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
Holds a L object. |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=cut |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
has name => ''; |
83
|
|
|
|
|
|
|
has log => sub { Mojo::Log->new }; |
84
|
|
|
|
|
|
|
has login => 0; |
85
|
|
|
|
|
|
|
has redis => sub { die 'redis connection required in constructor' }; |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
my @ADD_MESSAGE_EVENTS = qw( irc_privmsg ctcp_action irc_notice ); |
88
|
|
|
|
|
|
|
my @ADD_SERVER_MESSAGE_EVENTS = qw( |
89
|
|
|
|
|
|
|
irc_rpl_yourhost irc_rpl_motdstart irc_rpl_motd irc_rpl_endofmotd |
90
|
|
|
|
|
|
|
irc_rpl_welcome rpl_luserclient |
91
|
|
|
|
|
|
|
); |
92
|
|
|
|
|
|
|
my @OTHER_EVENTS = qw( |
93
|
|
|
|
|
|
|
irc_rpl_welcome irc_rpl_myinfo irc_join irc_nick irc_part irc_479 |
94
|
|
|
|
|
|
|
irc_rpl_whoisuser irc_rpl_whoisidle irc_rpl_whoischannels irc_rpl_endofwhois |
95
|
|
|
|
|
|
|
irc_rpl_topic irc_topic |
96
|
|
|
|
|
|
|
irc_rpl_topicwhotime irc_rpl_notopic err_nosuchchannel err_nosuchnick |
97
|
|
|
|
|
|
|
err_notonchannel err_bannedfromchan irc_rpl_list |
98
|
|
|
|
|
|
|
irc_rpl_listend irc_mode irc_quit irc_kick irc_error |
99
|
|
|
|
|
|
|
irc_rpl_namreply irc_rpl_endofnames err_nicknameinuse |
100
|
|
|
|
|
|
|
); |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
has _irc => sub { |
103
|
|
|
|
|
|
|
my $self = shift; |
104
|
|
|
|
|
|
|
my $irc = Mojo::IRC->new(debug_key => join ':', $self->login, $self->name); |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
$irc->parser(Parse::IRC->new(ctcp => 1)); |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
Scalar::Util::weaken($self); |
109
|
|
|
|
|
|
|
$irc->register_default_event_handlers; |
110
|
|
|
|
|
|
|
$irc->on(close => sub { $self->_irc_close }); |
111
|
|
|
|
|
|
|
$irc->on(error => sub { $self->_irc_error($_[1]) }); |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
for my $event (@ADD_MESSAGE_EVENTS) { |
114
|
|
|
|
|
|
|
$irc->on($event => sub { $self->add_message($_[1]) }); |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
for my $event (@ADD_SERVER_MESSAGE_EVENTS) { |
117
|
|
|
|
|
|
|
$irc->on($event => sub { $self->add_server_message($_[1]) }); |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
for my $event (@OTHER_EVENTS) { |
120
|
|
|
|
|
|
|
$irc->on($event => sub { $_[1]->{handled}++ or $self->$event($_[1]) }); |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
$irc; |
124
|
|
|
|
|
|
|
}; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
sub _irc_close { |
127
|
0
|
|
|
0
|
|
|
my $self = shift; |
128
|
0
|
|
|
|
|
|
my $name = $self->_irc->name; |
129
|
|
|
|
|
|
|
|
130
|
0
|
|
|
|
|
|
$self->_state('disconnected'); |
131
|
|
|
|
|
|
|
|
132
|
0
|
0
|
|
|
|
|
if ($self->{stop}) { |
133
|
0
|
|
|
|
|
|
$self->_publish_and_save(server_message => {status => 200, message => 'Disconnected.'}); |
134
|
0
|
|
|
|
|
|
return; |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
0
|
|
|
|
|
|
$self->_publish_and_save(server_message => {status => 500, message => "Disconnected from $name."}); |
138
|
0
|
|
|
|
|
|
$self->_reconnect; |
139
|
|
|
|
|
|
|
} |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
sub _irc_error { |
142
|
0
|
|
|
0
|
|
|
my ($self, $error) = @_; |
143
|
0
|
|
|
|
|
|
my $name = $self->_irc->name; |
144
|
|
|
|
|
|
|
|
145
|
0
|
0
|
|
|
|
|
$self->{stop} and return $self->_state('disconnected'); |
146
|
0
|
|
|
|
|
|
$self->_state('disconnected'); |
147
|
0
|
|
|
|
|
|
$self->_publish_and_save(server_message => {status => 500, message => "Connection to $name failed: $error"}); |
148
|
0
|
|
|
|
|
|
$self->_reconnect; |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=head1 METHODS |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
=head2 new |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
Checks for mandatory attributes: L and L. |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=cut |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
sub new { |
160
|
0
|
|
|
0
|
1
|
|
my $self = shift->SUPER::new(@_); |
161
|
|
|
|
|
|
|
|
162
|
0
|
0
|
|
|
|
|
$self->{login} or die "login is required"; |
163
|
0
|
0
|
|
|
|
|
$self->{name} or die "name is required"; |
164
|
0
|
|
|
|
|
|
$self->{conversation_path} = "user:$self->{login}:conversations"; |
165
|
0
|
|
|
|
|
|
$self->{path} = "user:$self->{login}:connection:$self->{name}"; |
166
|
0
|
|
|
|
|
|
$self->{state} = 'disconnected'; |
167
|
0
|
|
|
|
|
|
$self; |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=head2 connect |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
$self = $self->connect; |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
This method will create a new L object with attribute data from |
175
|
|
|
|
|
|
|
L. The values fetched from the backend is identified by L and |
176
|
|
|
|
|
|
|
L. This method then call L after the object is set |
177
|
|
|
|
|
|
|
up. |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
Attributes fetched from backend: nick, user, host and channels. The latter |
180
|
|
|
|
|
|
|
is set in L and used by L. |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=cut |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
sub connect { |
185
|
0
|
|
|
0
|
1
|
|
my ($self) = @_; |
186
|
0
|
|
|
|
|
|
my $irc = $self->_irc; |
187
|
|
|
|
|
|
|
|
188
|
0
|
|
|
|
|
|
Scalar::Util::weaken($self); |
189
|
0
|
|
|
|
|
|
$self->{core_connect_timer} = 0; |
190
|
0
|
|
0
|
0
|
|
|
$self->{keepnick_tid} ||= $irc->ioloop->recurring(60 => sub { $self->_steal_nick }); |
|
0
|
|
|
|
|
|
|
191
|
0
|
|
|
|
|
|
$self->_subscribe; |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
$self->redis->execute( |
194
|
|
|
|
|
|
|
[hgetall => $self->{path}], |
195
|
|
|
|
|
|
|
[get => 'convos:frontend:url'], |
196
|
|
|
|
|
|
|
sub { |
197
|
0
|
|
|
0
|
|
|
my ($redis, $args, $url) = @_; |
198
|
0
|
0
|
|
|
|
|
$self->redis->hset($self->{path} => tls => $self->{disable_tls} ? 0 : 1); |
199
|
0
|
|
0
|
|
|
|
$irc->name($url || 'Convos'); |
200
|
0
|
|
0
|
|
|
|
$irc->nick($args->{nick} || $self->login); |
201
|
0
|
0
|
|
|
|
|
$irc->pass($args->{password}) if $args->{password}; |
202
|
0
|
|
0
|
|
|
|
$irc->server($args->{server} || $args->{host}); |
203
|
0
|
0
|
|
|
|
|
$irc->tls($self->{disable_tls} ? undef : {}); |
204
|
0
|
|
0
|
|
|
|
$irc->user($args->{username} || $self->login); |
205
|
|
|
|
|
|
|
$irc->connect( |
206
|
|
|
|
|
|
|
sub { |
207
|
0
|
|
|
|
|
|
my ($irc, $error) = @_; |
208
|
0
|
0
|
|
|
|
|
$error and return $self->_connect_failed($error); |
209
|
0
|
|
|
|
|
|
$self->_publish_and_save(server_message => {status => 200, message => "Connected to IRC server"}); |
210
|
0
|
|
|
|
|
|
$self->_state('connected'); |
211
|
|
|
|
|
|
|
}, |
212
|
0
|
|
|
|
|
|
); |
213
|
|
|
|
|
|
|
}, |
214
|
0
|
|
|
|
|
|
); |
215
|
|
|
|
|
|
|
|
216
|
0
|
|
|
|
|
|
$self; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
sub _state { |
220
|
0
|
|
|
0
|
|
|
my ($self, $state) = @_; |
221
|
|
|
|
|
|
|
|
222
|
0
|
|
|
|
|
|
$self->{state} = $state; |
223
|
0
|
|
|
|
|
|
$self->redis->hset($self->{path}, state => $state); |
224
|
0
|
|
|
|
|
|
$self; |
225
|
|
|
|
|
|
|
} |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
sub _steal_nick { |
228
|
0
|
|
|
0
|
|
|
my $self = shift; |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
# We will try to "steal" the nich we really want every 60 second |
231
|
|
|
|
|
|
|
Mojo::IOLoop->delay( |
232
|
|
|
|
|
|
|
sub { |
233
|
0
|
|
|
0
|
|
|
my ($delay) = @_; |
234
|
0
|
|
|
|
|
|
$self->redis->hget($self->{path}, 'nick', $delay->begin); |
235
|
|
|
|
|
|
|
}, |
236
|
|
|
|
|
|
|
sub { |
237
|
0
|
|
|
0
|
|
|
my ($delay, $nick) = @_; |
238
|
0
|
0
|
0
|
|
|
|
$self->_irc->write(NICK => $nick) if $nick and $self->_irc->nick ne $nick; |
239
|
|
|
|
|
|
|
} |
240
|
0
|
|
|
|
|
|
); |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
sub _subscribe { |
244
|
0
|
|
|
0
|
|
|
my $self = shift; |
245
|
0
|
|
|
|
|
|
my $irc = $self->_irc; |
246
|
|
|
|
|
|
|
|
247
|
0
|
|
|
|
|
|
Scalar::Util::weaken($self); |
248
|
0
|
|
|
|
|
|
$self->{messages} = $self->redis->subscribe("convos:user:@{[$self->login]}:@{[$self->name]}"); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
$self->{messages}->on( |
250
|
|
|
|
|
|
|
error => sub { |
251
|
0
|
|
|
0
|
|
|
my ($sub, $error) = @_; |
252
|
0
|
|
|
|
|
|
$self->log->warn("[$self->{path}] Re-subcribing to messages to @{[$irc->name]}. ($error)"); |
|
0
|
|
|
|
|
|
|
253
|
0
|
|
|
|
|
|
$self->_subscribe; |
254
|
|
|
|
|
|
|
}, |
255
|
0
|
|
|
|
|
|
); |
256
|
|
|
|
|
|
|
$self->{messages}->on( |
257
|
|
|
|
|
|
|
message => sub { |
258
|
0
|
|
|
0
|
|
|
my ($sub, $raw_message) = @_; |
259
|
0
|
|
|
|
|
|
my ($uuid, $message); |
260
|
|
|
|
|
|
|
|
261
|
0
|
|
|
|
|
|
$raw_message =~ s/(\S+)\s//; |
262
|
0
|
|
|
|
|
|
$uuid = $1; |
263
|
0
|
|
|
|
|
|
$raw_message = sprintf ':%s %s', $irc->nick, $raw_message; |
264
|
0
|
|
|
|
|
|
$message = Parse::IRC::parse_irc($raw_message); |
265
|
|
|
|
|
|
|
|
266
|
0
|
0
|
|
|
|
|
unless (ref $message) { |
267
|
0
|
|
|
|
|
|
$self->_publish_and_save( |
268
|
|
|
|
|
|
|
server_message => {status => 400, message => "Unable to parse: $raw_message", uuid => $uuid}); |
269
|
0
|
|
|
|
|
|
return; |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
|
272
|
0
|
|
|
|
|
|
$message->{uuid} = $uuid; |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
$irc->write( |
275
|
|
|
|
|
|
|
$raw_message, |
276
|
|
|
|
|
|
|
sub { |
277
|
0
|
|
|
|
|
|
my ($irc, $error) = @_; |
278
|
|
|
|
|
|
|
|
279
|
0
|
0
|
|
|
|
|
if ($error) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
280
|
0
|
|
|
|
|
|
$self->_publish_and_save(server_message => |
281
|
0
|
|
|
|
|
|
{status => 500, message => "Could not send message to @{[$irc->name]}: $error", uuid => $uuid}); |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
elsif ($message->{command} eq 'PRIVMSG') { |
284
|
0
|
|
|
|
|
|
$self->add_message($message); |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
elsif (my $method = $self->can('cmd_' . lc $message->{command})) { |
287
|
0
|
|
|
|
|
|
$self->$method($message); |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
} |
290
|
0
|
|
|
|
|
|
); |
291
|
|
|
|
|
|
|
} |
292
|
0
|
|
|
|
|
|
); |
293
|
|
|
|
|
|
|
|
294
|
0
|
|
|
|
|
|
$self; |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
=head2 channels_from_conversations |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
@channels = $self->channels_from_conversations(\@conversations); |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
This method returns an array ref of channels based on the conversations |
302
|
|
|
|
|
|
|
input. It will use L to filter out the right list. |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=cut |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
sub channels_from_conversations { |
307
|
0
|
|
|
0
|
1
|
|
my ($self, $conversations) = @_; |
308
|
|
|
|
|
|
|
|
309
|
0
|
0
|
|
|
|
|
map { lc $_->[1] } grep { $_->[0] eq $self->name and $_->[1] =~ /^[#&]/ } map { [id_as $_ ] } @{$conversations || []}; |
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
=head2 add_server_message |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
$self->add_server_message(\%message); |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
Will look at L<%message> and add it to the database as a server message |
317
|
|
|
|
|
|
|
if it looks like one. Returns true if the message was added to redis. |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
=cut |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
sub add_server_message { |
322
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
323
|
0
|
|
|
|
|
|
my $params = $message->{params}; |
324
|
0
|
|
|
|
|
|
my $data = {status => 200}; |
325
|
|
|
|
|
|
|
|
326
|
0
|
|
|
|
|
|
shift @$params; # I think this removes our own nick... Not quite sure though |
327
|
0
|
|
|
|
|
|
$data->{message} = join ' ', @$params; |
328
|
0
|
|
0
|
|
|
|
$message->{command} ||= ''; |
329
|
|
|
|
|
|
|
|
330
|
0
|
|
|
|
|
|
$self->_state('connected'); |
331
|
0
|
|
|
|
|
|
$self->_publish_and_save(server_message => $data); |
332
|
|
|
|
|
|
|
} |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
=head2 add_message |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
$self->add_message(\%message); |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
Will add a private message to the database. |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
=cut |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
sub add_message { |
343
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
344
|
0
|
|
|
|
|
|
my $current_nick = $self->_irc->nick; |
345
|
0
|
|
|
|
|
|
my $is_private_message = $message->{params}[0] eq $current_nick; |
346
|
0
|
|
|
|
|
|
my $data = {highlight => 0, message => $message->{params}[1], timestamp => time, uuid => $message->{uuid},}; |
347
|
|
|
|
|
|
|
|
348
|
0
|
0
|
|
|
|
|
@$data{qw( nick user host )} = IRC::Utils::parse_user($message->{prefix}) if $message->{prefix}; |
349
|
0
|
0
|
|
|
|
|
$data->{target} = lc($is_private_message ? $data->{nick} : $message->{params}[0]); |
350
|
0
|
|
0
|
|
|
|
$data->{host} ||= 'localhost'; |
351
|
|
|
|
|
|
|
|
352
|
0
|
0
|
|
|
|
|
if ($data->{nick}) { |
353
|
0
|
0
|
0
|
|
|
|
if ($data->{nick} eq $current_nick) { |
|
|
0
|
|
|
|
|
|
354
|
0
|
|
0
|
|
|
|
$data->{user} ||= $self->_irc->user; |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
elsif ($is_private_message or $data->{message} =~ /\b$current_nick\b/) { |
357
|
0
|
0
|
0
|
|
|
|
$self->_add_conversation($data->{target}) if $is_private_message and $data->{user}; |
358
|
0
|
|
|
|
|
|
$data->{highlight} = 1; |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
} |
361
|
|
|
|
|
|
|
|
362
|
0
|
0
|
|
|
|
|
if (!$data->{user}) { # server notice/message |
363
|
0
|
|
|
|
|
|
return $self->add_server_message($message); |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
# need to take care of when the current user also writes /me... |
367
|
|
|
|
|
|
|
# this is not yet tested, since i have no time right now :( |
368
|
0
|
0
|
|
|
|
|
if ($data->{message} =~ s/\x{1}ACTION (.*)\x{1}/$1/) { |
369
|
0
|
|
|
|
|
|
$message->{command} = "CTCP_ACTION"; |
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
|
372
|
0
|
0
|
|
|
|
|
$self->_publish_and_save($message->{command} eq 'CTCP_ACTION' ? 'action_message' : 'message', $data); |
373
|
|
|
|
|
|
|
} |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
sub _add_conversation { |
376
|
0
|
|
|
0
|
|
|
my ($self, $target) = @_; |
377
|
0
|
|
|
|
|
|
my $name = as_id $self->name, $target; |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
Mojo::IOLoop->delay( |
380
|
|
|
|
|
|
|
sub { |
381
|
0
|
|
|
0
|
|
|
my ($delay) = @_; |
382
|
0
|
|
|
|
|
|
$self->redis->zincrby($self->{conversation_path}, 0, $name, $delay->begin); |
383
|
|
|
|
|
|
|
}, |
384
|
|
|
|
|
|
|
sub { |
385
|
0
|
|
|
0
|
|
|
my ($delay, $part_of_conversation_list) = @_; |
386
|
0
|
0
|
|
|
|
|
$part_of_conversation_list and return; |
387
|
0
|
|
|
|
|
|
$self->redis->zrevrange($self->{conversation_path}, 0, 0, 'WITHSCORES', $delay->begin); |
388
|
|
|
|
|
|
|
}, |
389
|
|
|
|
|
|
|
sub { |
390
|
0
|
|
|
0
|
|
|
my ($delay, $score) = @_; |
391
|
0
|
|
|
|
|
|
$self->redis->zadd($self->{conversation_path}, $score->[1] - 0.0001, $name, $delay->begin); |
392
|
|
|
|
|
|
|
}, |
393
|
|
|
|
|
|
|
sub { |
394
|
0
|
|
|
0
|
|
|
my ($delay) = @_; |
395
|
0
|
|
|
|
|
|
$self->_publish(add_conversation => {target => $target}); |
396
|
|
|
|
|
|
|
}, |
397
|
0
|
|
|
|
|
|
); |
398
|
|
|
|
|
|
|
} |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
=head2 disconnect |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
Will disconnect from the L server. |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
=cut |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
sub disconnect { |
407
|
0
|
|
|
0
|
1
|
|
my ($self, $cb) = @_; |
408
|
0
|
|
|
|
|
|
$self->{stop} = 1; |
409
|
0
|
|
0
|
0
|
|
|
$self->_irc->disconnect($cb || sub { }); |
|
0
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
=head1 EVENT HANDLERS |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
=head2 irc_rpl_welcome |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
Example message: |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
:Zurich.CH.EU.Undernet.Org 001 somenick :Welcome to the UnderNet IRC Network, somenick |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=cut |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
sub irc_rpl_welcome { |
423
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
424
|
|
|
|
|
|
|
|
425
|
0
|
|
|
|
|
|
$self->{attempts} = 0; |
426
|
|
|
|
|
|
|
|
427
|
0
|
|
|
|
|
|
Scalar::Util::weaken($self); |
428
|
|
|
|
|
|
|
$self->redis->zrange( |
429
|
|
|
|
|
|
|
$self->{conversation_path}, |
430
|
|
|
|
|
|
|
0, -1, |
431
|
|
|
|
|
|
|
sub { |
432
|
0
|
|
|
0
|
|
|
for my $channel ($self->channels_from_conversations($_[1])) { |
433
|
|
|
|
|
|
|
$self->redis->hget( |
434
|
|
|
|
|
|
|
"$self->{path}:$channel", |
435
|
|
|
|
|
|
|
key => sub { |
436
|
0
|
0
|
|
|
|
|
$_[1] ? $self->_irc->write(JOIN => $channel, $_[1]) : $self->_irc->write(JOIN => $channel); |
437
|
|
|
|
|
|
|
} |
438
|
0
|
|
|
|
|
|
); |
439
|
|
|
|
|
|
|
} |
440
|
|
|
|
|
|
|
} |
441
|
0
|
|
|
|
|
|
); |
442
|
|
|
|
|
|
|
} |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
=head2 irc_rpl_endofwhois |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
Use data from L, L and |
447
|
|
|
|
|
|
|
L. |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
=cut |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
sub irc_rpl_endofwhois { |
452
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
453
|
0
|
|
|
|
|
|
my $nick = $message->{params}[1]; |
454
|
0
|
|
0
|
|
|
|
my $whois = delete $self->{whois}{$nick} || {}; |
455
|
|
|
|
|
|
|
|
456
|
0
|
|
0
|
|
|
|
$whois->{channels} ||= []; |
457
|
0
|
|
0
|
|
|
|
$whois->{idle} ||= 0; |
458
|
0
|
|
0
|
|
|
|
$whois->{realname} ||= ''; |
459
|
0
|
|
0
|
|
|
|
$whois->{user} ||= ''; |
460
|
0
|
|
|
|
|
|
$whois->{nick} = $nick; |
461
|
0
|
0
|
|
|
|
|
$self->_publish(whois => $whois) if $whois->{host}; |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
=head2 irc_rpl_whoisidle |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
Store idle info internally. See L. |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=cut |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
sub irc_rpl_whoisidle { |
471
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
472
|
0
|
|
|
|
|
|
my $nick = $message->{params}[1]; |
473
|
|
|
|
|
|
|
|
474
|
0
|
|
0
|
|
|
|
$self->{whois}{$nick}{idle} = $message->{params}[2] || 0; |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
=head2 irc_rpl_whoisuser |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
Store user info internally. See L. |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
=cut |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
sub irc_rpl_whoisuser { |
484
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
485
|
0
|
|
|
|
|
|
my $params = $message->{params}; |
486
|
0
|
|
|
|
|
|
my $nick = $params->[1]; |
487
|
|
|
|
|
|
|
|
488
|
0
|
|
|
|
|
|
$self->{whois}{$nick}{host} = $params->[3]; |
489
|
0
|
|
|
|
|
|
$self->{whois}{$nick}{realname} = $params->[5]; |
490
|
0
|
|
|
|
|
|
$self->{whois}{$nick}{user} = $params->[2]; |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
=head2 irc_rpl_whoischannels |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
Reply with user channels |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
=cut |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
sub irc_rpl_whoischannels { |
500
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
501
|
0
|
|
|
|
|
|
my $nick = $message->{params}[1]; |
502
|
|
|
|
|
|
|
|
503
|
0
|
|
0
|
|
|
|
push @{$self->{whois}{$nick}{channels}}, split ' ', $message->{params}[2] || ''; |
|
0
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
} |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=head2 irc_rpl_notopic |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
:server 331 nick #channel :No topic is set. |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
=cut |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
sub irc_rpl_notopic { |
513
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
514
|
0
|
|
|
|
|
|
my $target = lc $message->{params}[1]; |
515
|
|
|
|
|
|
|
|
516
|
0
|
|
|
|
|
|
$self->redis->hset("$self->{path}:$target", topic => ''); |
517
|
0
|
|
|
|
|
|
$self->_publish(topic => {topic => '', target => $target}); |
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
=head2 irc_rpl_topic |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
Reply with topic |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
=cut |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
sub irc_rpl_topic { |
527
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
528
|
0
|
|
|
|
|
|
my $target = lc $message->{params}[1]; |
529
|
0
|
|
|
|
|
|
my $topic = $message->{params}[2]; |
530
|
|
|
|
|
|
|
|
531
|
0
|
|
|
|
|
|
$self->redis->hset("$self->{path}:$target", topic => $topic); |
532
|
0
|
|
|
|
|
|
$self->_publish(topic => {topic => $topic, target => $target}); |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
=head2 irc_topic |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
:nick!~user@hostname TOPIC #channel :some topic |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
=cut |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
sub irc_topic { |
542
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
543
|
0
|
|
|
|
|
|
my $target = lc $message->{params}[0]; |
544
|
0
|
|
|
|
|
|
my $topic = $message->{params}[1]; |
545
|
|
|
|
|
|
|
|
546
|
0
|
|
|
|
|
|
$self->redis->hset("$self->{path}:$target", topic => $topic); |
547
|
0
|
|
|
|
|
|
$self->_publish(topic => {topic => $topic, target => $target}); |
548
|
|
|
|
|
|
|
} |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
=head2 irc_rpl_topicwhotime |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
Reply with who and when for topic change |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
=cut |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
sub irc_rpl_topicwhotime { |
557
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
558
|
|
|
|
|
|
|
|
559
|
0
|
|
|
|
|
|
$self->_publish(topic_by => |
560
|
|
|
|
|
|
|
{timestamp => $message->{params}[3], nick => $message->{params}[2], target => lc $message->{params}[1],}); |
561
|
|
|
|
|
|
|
} |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=head2 irc_rpl_myinfo |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
Example message: |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
:Tampa.FL.US.Undernet.org 004 somenick Tampa.FL.US.Undernet.org u2.10.12.14 dioswkgx biklmnopstvrDR bklov |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
=cut |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
sub irc_rpl_myinfo { |
572
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
573
|
0
|
|
|
|
|
|
my @keys = qw/ current_nick real_host version available_user_modes available_channel_modes /; |
574
|
0
|
|
|
|
|
|
my $i = 0; |
575
|
|
|
|
|
|
|
|
576
|
0
|
|
0
|
|
|
|
$self->redis->hmset($self->{path}, map { $_, $message->{params}[$i++] // '' } @keys); |
|
0
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
=head2 irc_479 |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
Invalid channel name. |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
=cut |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
sub irc_479 { |
586
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
# params => [ 'nickname', '1', 'Illegal channel name' ], |
589
|
0
|
|
0
|
|
|
|
$self->_publish(server_message => {status => 400, message => $message->{params}[2] || 'Illegal channel name'}); |
590
|
|
|
|
|
|
|
} |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
=head2 irc_join |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
See L. |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
=cut |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
sub irc_join { |
599
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
600
|
0
|
|
|
|
|
|
my ($nick, $user, $host) = IRC::Utils::parse_user($message->{prefix}); |
601
|
0
|
|
|
|
|
|
my $channel = lc $message->{params}[0]; |
602
|
|
|
|
|
|
|
|
603
|
0
|
0
|
|
|
|
|
if ($nick eq $self->_irc->nick) { |
604
|
0
|
|
|
|
|
|
$self->redis->hset("$self->{path}:$channel", topic => ''); |
605
|
0
|
|
|
|
|
|
$self->redis->hset("convos:host2convos" => $host => 'loopback'); |
606
|
0
|
|
|
|
|
|
$self->_add_conversation($channel); |
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
else { |
609
|
0
|
|
|
|
|
|
$self->_publish(nick_joined => {nick => $nick, target => $channel}); |
610
|
|
|
|
|
|
|
} |
611
|
|
|
|
|
|
|
} |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
=head2 irc_nick |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
:old_nick!~username@1.2.3.4 NICK :new_nick |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
=cut |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
sub irc_nick { |
620
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
621
|
0
|
|
|
|
|
|
my ($old_nick) = IRC::Utils::parse_user($message->{prefix}); |
622
|
0
|
|
|
|
|
|
my $new_nick = $message->{params}[0]; |
623
|
|
|
|
|
|
|
|
624
|
0
|
0
|
|
|
|
|
if ($new_nick eq $self->_irc->nick) { |
625
|
0
|
|
|
|
|
|
delete $self->{supress}{err_nicknameinuse}; |
626
|
0
|
|
|
|
|
|
$self->redis->hset($self->{path}, current_nick => $new_nick); |
627
|
|
|
|
|
|
|
} |
628
|
|
|
|
|
|
|
|
629
|
0
|
|
|
|
|
|
$self->_publish(nick_change => {old_nick => $old_nick, new_nick => $new_nick}); |
630
|
|
|
|
|
|
|
} |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
=head2 irc_quit |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
{ |
635
|
|
|
|
|
|
|
params => [ 'Quit: leaving' ], |
636
|
|
|
|
|
|
|
raw_line => ':nick!~user@localhost QUIT :Quit: leaving', |
637
|
|
|
|
|
|
|
command => 'QUIT', |
638
|
|
|
|
|
|
|
prefix => 'nick!~user@localhost' |
639
|
|
|
|
|
|
|
}; |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
=cut |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
sub irc_quit { |
644
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
645
|
0
|
|
|
|
|
|
my ($nick) = IRC::Utils::parse_user($message->{prefix}); |
646
|
|
|
|
|
|
|
|
647
|
0
|
|
|
|
|
|
Scalar::Util::weaken($self); |
648
|
0
|
|
|
|
|
|
$self->_publish(nick_quit => {nick => $nick, message => $message->{params}[0]}); |
649
|
|
|
|
|
|
|
} |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
=head2 irc_kick |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
'raw_line' => ':testing!~marcus@home.means.no KICK #testmore :marcus_', |
654
|
|
|
|
|
|
|
'params' => [ '#testmore', 'marcus_' ], |
655
|
|
|
|
|
|
|
'command' => 'KICK', |
656
|
|
|
|
|
|
|
'handled' => 1, |
657
|
|
|
|
|
|
|
'prefix' => 'testing!~marcus@40.101.45.31.customer.cdi.no' |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
=cut |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
sub irc_kick { |
662
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
663
|
0
|
|
|
|
|
|
my ($by) = IRC::Utils::parse_user($message->{prefix}); |
664
|
0
|
|
|
|
|
|
my $channel = lc $message->{params}[0]; |
665
|
0
|
|
|
|
|
|
my $nick = $message->{params}[1]; |
666
|
|
|
|
|
|
|
|
667
|
0
|
0
|
|
|
|
|
if ($nick eq $self->_irc->nick) { |
668
|
0
|
|
|
|
|
|
my $name = as_id $self->name, $channel; |
669
|
0
|
|
|
0
|
|
|
$self->redis->zrem($self->{conversation_path}, $name, sub { }); |
|
0
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
} |
671
|
|
|
|
|
|
|
|
672
|
0
|
|
|
|
|
|
$self->_publish(nick_kicked => {by => $by, nick => $nick, target => $channel}); |
673
|
|
|
|
|
|
|
} |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
=head2 irc_part |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
=cut |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
sub irc_part { |
680
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
681
|
0
|
|
|
|
|
|
my ($nick) = IRC::Utils::parse_user($message->{prefix}); |
682
|
0
|
|
|
|
|
|
my $channel = lc $message->{params}[0]; |
683
|
|
|
|
|
|
|
|
684
|
0
|
|
|
|
|
|
Scalar::Util::weaken($self); |
685
|
0
|
0
|
|
|
|
|
if ($nick eq $self->_irc->nick) { |
686
|
0
|
|
|
|
|
|
my $name = as_id $self->name, $channel; |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
$self->redis->zrem( |
689
|
|
|
|
|
|
|
$self->{conversation_path}, |
690
|
|
|
|
|
|
|
$name, |
691
|
|
|
|
|
|
|
sub { |
692
|
0
|
|
|
0
|
|
|
$self->_publish(remove_conversation => {target => $channel}); |
693
|
|
|
|
|
|
|
} |
694
|
0
|
|
|
|
|
|
); |
695
|
|
|
|
|
|
|
} |
696
|
|
|
|
|
|
|
else { |
697
|
0
|
|
|
|
|
|
$self->_publish(nick_parted => {nick => $nick, target => $channel}); |
698
|
|
|
|
|
|
|
} |
699
|
|
|
|
|
|
|
} |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
=head2 err_bannedfromchan |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
:electret.shadowcat.co.uk 474 nick #channel :Cannot join channel (+b) |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
=cut |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
sub err_bannedfromchan { |
708
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
709
|
0
|
|
|
|
|
|
my $channel = lc $message->{params}[1]; |
710
|
0
|
|
|
|
|
|
my $name = as_id $self->name, $channel; |
711
|
|
|
|
|
|
|
|
712
|
0
|
|
|
|
|
|
$self->_publish_and_save(server_message => {status => 401, message => $message->{params}[2]}); |
713
|
|
|
|
|
|
|
|
714
|
0
|
|
|
|
|
|
Scalar::Util::weaken($self); |
715
|
|
|
|
|
|
|
$self->redis->zrem( |
716
|
|
|
|
|
|
|
$self->{conversation_path}, |
717
|
|
|
|
|
|
|
$name, |
718
|
|
|
|
|
|
|
sub { |
719
|
0
|
|
|
0
|
|
|
$self->_publish(remove_conversation => {target => $channel}); |
720
|
|
|
|
|
|
|
} |
721
|
0
|
|
|
|
|
|
); |
722
|
|
|
|
|
|
|
} |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
=head2 err_nicknameinuse |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
=cut |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
sub err_nicknameinuse { |
729
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
730
|
|
|
|
|
|
|
|
731
|
0
|
0
|
|
|
|
|
if ($self->{supress}{err_nicknameinuse}++) { |
732
|
0
|
|
|
|
|
|
return; |
733
|
|
|
|
|
|
|
} |
734
|
|
|
|
|
|
|
|
735
|
0
|
|
|
|
|
|
$self->_publish(server_message => {status => 500, message => $message->{params}[2],}); |
736
|
|
|
|
|
|
|
} |
737
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
=head2 err_nosuchchannel |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
:astral.shadowcat.co.uk 403 nick #channel :No such channel |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
=cut |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
sub err_nosuchchannel { |
745
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
746
|
0
|
|
|
|
|
|
my $channel = lc $message->{params}[1]; |
747
|
0
|
|
|
|
|
|
my $name = as_id $self->name, $channel; |
748
|
|
|
|
|
|
|
|
749
|
0
|
|
|
|
|
|
$self->_publish(server_message => {status => 400, message => qq(No such channel "$channel")}); |
750
|
|
|
|
|
|
|
|
751
|
0
|
0
|
|
|
|
|
if ($channel =~ /^[#&]/) { |
752
|
0
|
|
|
|
|
|
Scalar::Util::weaken($self); |
753
|
|
|
|
|
|
|
$self->redis->zrem( |
754
|
|
|
|
|
|
|
$self->{conversation_path}, |
755
|
|
|
|
|
|
|
$name, |
756
|
|
|
|
|
|
|
sub { |
757
|
0
|
|
|
0
|
|
|
$self->_publish(remove_conversation => {target => $channel}); |
758
|
|
|
|
|
|
|
} |
759
|
0
|
|
|
|
|
|
); |
760
|
|
|
|
|
|
|
} |
761
|
|
|
|
|
|
|
} |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
=head2 err_nosuchnick |
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
:electret.shadowcat.co.uk 442 sender nick :No such nick |
766
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
=cut |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
sub err_nosuchnick { |
770
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
771
|
|
|
|
|
|
|
|
772
|
0
|
|
|
|
|
|
$self->_publish(err_nosuchnick => {nick => $message->{params}[1]}); |
773
|
|
|
|
|
|
|
} |
774
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
=head2 err_notonchannel |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
:electret.shadowcat.co.uk 442 nick #channel :You're not on that channel |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
=cut |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
sub err_notonchannel { |
782
|
0
|
|
|
0
|
1
|
|
shift->err_nosuchchannel(@_); |
783
|
|
|
|
|
|
|
} |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
=head2 irc_rpl_endofnames |
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
Example message: |
788
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
:magnet.llarian.net 366 somenick #channel :End of /NAMES list. |
790
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
=cut |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
sub irc_rpl_endofnames { |
794
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
795
|
0
|
0
|
|
|
|
|
my $channel = lc $message->{params}[1] or return; |
796
|
0
|
|
0
|
|
|
|
my $nicks = delete $self->{nicks}{$channel} || []; |
797
|
|
|
|
|
|
|
|
798
|
0
|
|
|
|
|
|
$self->_publish(rpl_namreply => {nicks => $nicks, target => $channel}); |
799
|
|
|
|
|
|
|
} |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
=head2 irc_rpl_namreply |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
Example message: |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
:Budapest.Hu.Eu.Undernet.org 353 somenick = #channel :somenick Indig0 Wildblue @HTML @CSS @Luch1an @Steaua_ Indig0_ Pilum @fade |
806
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
=cut |
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
sub irc_rpl_namreply { |
810
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
811
|
0
|
0
|
|
|
|
|
my $channel = lc $message->{params}[2] or return; |
812
|
0
|
|
0
|
|
|
|
my $nicks = $self->{nicks}{$channel} ||= []; |
813
|
|
|
|
|
|
|
|
814
|
0
|
|
|
|
|
|
for my $nick (sort { lc $a cmp lc $b } split /\s+/, $message->{params}[3]) { # 3 = "+nick0 @nick1 nick2" |
|
0
|
|
|
|
|
|
|
815
|
0
|
0
|
|
|
|
|
my $mode = $nick =~ s/^([@~+*])// ? $1 : ''; |
816
|
0
|
|
|
|
|
|
push @$nicks, {nick => $nick, mode => $mode}; |
817
|
|
|
|
|
|
|
} |
818
|
|
|
|
|
|
|
} |
819
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
=head2 irc_rpl_list |
821
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
:servername 322 somenick #channel 10 :[+n] some topic |
823
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
=cut |
825
|
|
|
|
|
|
|
|
826
|
|
|
|
|
|
|
sub irc_rpl_list { |
827
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
828
|
0
|
|
|
|
|
|
my $network = $self->name; |
829
|
0
|
|
|
|
|
|
my $name = $message->{params}[1]; |
830
|
0
|
|
0
|
|
|
|
my %info = (name => $name, visible => $message->{params}[2], title => $message->{params}[3] // ''); |
831
|
|
|
|
|
|
|
|
832
|
0
|
|
|
|
|
|
$self->_publish(channel_info => {name => $name, network => $network, info => \%info}); |
833
|
0
|
0
|
|
|
|
|
$self->redis->hset("convos:irc:$network:channels", $name => j \%info) if $self->{save_channels}; |
834
|
|
|
|
|
|
|
} |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
=head2 irc_rpl_listend |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
:servername 323 somenick :End of /LIST |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
=cut |
841
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
sub irc_rpl_listend { |
843
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
844
|
0
|
|
|
|
|
|
my $network = $self->name; |
845
|
|
|
|
|
|
|
|
846
|
0
|
0
|
|
|
|
|
$self->redis->expire("convos:irc:$network:channels", CHANNEL_LIST_CACHE_TIMEOUT) if delete $self->{save_channels}; |
847
|
|
|
|
|
|
|
} |
848
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
=head2 irc_mode |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
:nick!user@host MODE #channel +o othernick |
852
|
|
|
|
|
|
|
:nick!user@host MODE yournick +i |
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
=cut |
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
sub irc_mode { |
857
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
858
|
0
|
|
|
|
|
|
my $target = lc shift @{$message->{params}}; |
|
0
|
|
|
|
|
|
|
859
|
0
|
|
|
|
|
|
my $mode = shift @{$message->{params}}; |
|
0
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
|
861
|
0
|
0
|
|
|
|
|
if ($target eq lc $self->_irc->nick) { |
862
|
0
|
|
|
|
|
|
$self->_publish(server_message => |
863
|
0
|
|
|
|
|
|
{status => 200, target => $self->name, message => "You are connected to @{[$self->name]} with mode $mode"}); |
864
|
|
|
|
|
|
|
} |
865
|
|
|
|
|
|
|
else { |
866
|
0
|
|
|
|
|
|
$self->_publish(mode => {target => $target, mode => $mode, args => join(' ', @{$message->{params}})}); |
|
0
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
} |
868
|
|
|
|
|
|
|
} |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
=head2 irc_error |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
Example message: |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
ERROR :Closing Link: somenick by Tampa.FL.US.Undernet.org (Sorry, your connection class is full - try again later or try another server) |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
=cut |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
sub irc_error { |
879
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
# Server dislikes us, we'll back off more |
882
|
0
|
|
|
|
|
|
$self->{attempts} += 10; |
883
|
0
|
|
|
|
|
|
$self->_publish_and_save(server_message => {status => 500, message => join(' ', @{$message->{params}})}); |
|
0
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
} |
885
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
=head2 cmd_nick |
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
Handle nick commands from user. Change nick and set new nick in redis. |
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
=cut |
891
|
|
|
|
|
|
|
|
892
|
|
|
|
|
|
|
sub cmd_nick { |
893
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
894
|
0
|
|
|
|
|
|
my $new_nick = $message->{params}[0]; |
895
|
|
|
|
|
|
|
|
896
|
0
|
0
|
|
|
|
|
if ($new_nick =~ /^[\w-]+$/) { |
897
|
0
|
|
|
|
|
|
$self->redis->hset($self->{path}, nick => $new_nick); |
898
|
0
|
|
|
|
|
|
$self->_publish(server_message => {status => 200, message => 'Set nick to ' . $new_nick}); |
899
|
|
|
|
|
|
|
} |
900
|
|
|
|
|
|
|
else { |
901
|
0
|
|
|
|
|
|
$self->_publish(server_message => {status => 400, message => 'Invalid nick'}); |
902
|
|
|
|
|
|
|
} |
903
|
|
|
|
|
|
|
} |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
=head2 cmd_join |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
Store keys on channel join. |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
=cut |
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
sub cmd_join { |
912
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
913
|
|
|
|
|
|
|
|
914
|
0
|
|
|
|
|
|
my $channel = $message->{params}[0]; |
915
|
0
|
0
|
|
|
|
|
if (my $key = $message->{params}[1]) { |
916
|
0
|
|
|
|
|
|
$self->redis->hset("$self->{path}:$channel", key => $key); |
917
|
|
|
|
|
|
|
} |
918
|
|
|
|
|
|
|
} |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
=head2 cmd_list |
921
|
|
|
|
|
|
|
|
922
|
|
|
|
|
|
|
=cut |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
sub cmd_list { |
925
|
0
|
|
|
0
|
1
|
|
my ($self, $message) = @_; |
926
|
0
|
|
|
|
|
|
my $network = $self->name; |
927
|
|
|
|
|
|
|
|
928
|
0
|
|
|
|
|
|
$self->{channels} = {}; |
929
|
|
|
|
|
|
|
|
930
|
0
|
0
|
0
|
|
|
|
if (my $filter = $message->{params}[0] || '') { |
931
|
0
|
|
|
|
|
|
$self->{channels}{lc($_)} = {name => $_, topic => '', not_found => 1} for split /,/, $filter; |
932
|
|
|
|
|
|
|
} |
933
|
|
|
|
|
|
|
else { |
934
|
0
|
|
|
|
|
|
$self->redis->del("convos:irc:$network:channels"); |
935
|
0
|
|
|
|
|
|
$self->{save_channels} = 1; |
936
|
|
|
|
|
|
|
} |
937
|
|
|
|
|
|
|
} |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
sub _connect_failed { |
940
|
0
|
|
|
0
|
|
|
my ($self, $error) = @_; |
941
|
0
|
|
|
|
|
|
my $server = $self->_irc->server; |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
# SSL connect attempt failed with unknown error |
944
|
|
|
|
|
|
|
# error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol |
945
|
0
|
0
|
|
|
|
|
if ($error =~ /SSL\d*_GET_SERVER_HELLO/) { |
946
|
0
|
|
|
|
|
|
$self->_state('reconnecting'); |
947
|
0
|
|
|
|
|
|
$self->_publish_and_save( |
948
|
|
|
|
|
|
|
server_message => {status => 400, message => "This IRC network ($server) does not support SSL/TLS."}); |
949
|
0
|
|
|
|
|
|
$self->{disable_tls} = 1; |
950
|
0
|
|
|
|
|
|
$self->{core_connect_timer} = 1; |
951
|
|
|
|
|
|
|
} |
952
|
|
|
|
|
|
|
else { |
953
|
0
|
|
|
|
|
|
$self->_state('disconnected'); |
954
|
0
|
|
|
|
|
|
$self->_publish_and_save(server_message => {status => 500, message => "Could not connect to $server: $error"}); |
955
|
0
|
|
|
|
|
|
$self->_reconnect; |
956
|
|
|
|
|
|
|
} |
957
|
|
|
|
|
|
|
} |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
sub _publish { |
960
|
0
|
|
|
0
|
|
|
my ($self, $event, $data) = @_; |
961
|
0
|
|
|
|
|
|
my $login = $self->login; |
962
|
0
|
|
|
|
|
|
my $name = $self->name; |
963
|
0
|
|
|
|
|
|
my $message; |
964
|
|
|
|
|
|
|
|
965
|
0
|
|
|
|
|
|
local $data->{state} = $self->{state}; |
966
|
|
|
|
|
|
|
|
967
|
0
|
|
|
|
|
|
$data->{event} = $event; |
968
|
0
|
|
|
|
|
|
$data->{network} = $name; |
969
|
0
|
|
0
|
|
|
|
$data->{timestamp} ||= time; |
970
|
0
|
|
0
|
|
|
|
$data->{uuid} ||= Mojo::Util::md5_sum($data->{timestamp} . $$); # not really an uuid |
971
|
0
|
|
|
|
|
|
$message = j $data; |
972
|
|
|
|
|
|
|
|
973
|
0
|
0
|
0
|
|
|
|
if ($event eq 'server_message' and $data->{status} != 200) { |
974
|
0
|
|
|
|
|
|
$self->log->warn("[$login:$name] $data->{message}"); |
975
|
|
|
|
|
|
|
} |
976
|
|
|
|
|
|
|
|
977
|
0
|
|
|
|
|
|
$self->redis->publish("convos:user:$login:out", $message); |
978
|
0
|
|
|
|
|
|
$message; |
979
|
|
|
|
|
|
|
} |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
sub _publish_and_save { |
982
|
0
|
|
|
0
|
|
|
my ($self, $event, $data) = @_; |
983
|
0
|
|
|
|
|
|
my $login = $self->login; |
984
|
0
|
|
|
|
|
|
my $message = $self->_publish($event, $data); |
985
|
|
|
|
|
|
|
|
986
|
0
|
0
|
|
|
|
|
if ($data->{highlight}) { |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
# Ooops! This must be broken: We're clearing the notification by index in |
989
|
|
|
|
|
|
|
# Client.pm, but the index we're clearing does not have to be the index in |
990
|
|
|
|
|
|
|
# the list. The bug should appear if we use an old ?notification=42 link |
991
|
|
|
|
|
|
|
# and in the meanwhile we have added more notifications..? |
992
|
0
|
|
|
|
|
|
$self->redis->lpush("user:$login:notifications", $message); |
993
|
|
|
|
|
|
|
} |
994
|
|
|
|
|
|
|
|
995
|
0
|
0
|
|
|
|
|
if ($data->{target}) { |
996
|
0
|
|
|
|
|
|
$self->redis->zadd("$self->{path}:$data->{target}:msg", $data->{timestamp}, $message); |
997
|
|
|
|
|
|
|
} |
998
|
|
|
|
|
|
|
else { |
999
|
0
|
|
|
|
|
|
$self->redis->zadd("$self->{path}:msg", $data->{timestamp}, $message); |
1000
|
|
|
|
|
|
|
} |
1001
|
|
|
|
|
|
|
|
1002
|
0
|
|
|
|
|
|
$self->emit(save => $data); |
1003
|
|
|
|
|
|
|
} |
1004
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
sub _reconnect { |
1006
|
0
|
|
|
0
|
|
|
my $self = shift; |
1007
|
0
|
|
|
|
|
|
$self->{attempts}++; |
1008
|
0
|
|
|
|
|
|
$self->{core_connect_timer} = 30 * $self->{attempts}; # CONNECT_INTERVAL * 30 = 60 seconds |
1009
|
|
|
|
|
|
|
} |
1010
|
|
|
|
|
|
|
|
1011
|
|
|
|
|
|
|
sub DESTROY { |
1012
|
0
|
|
|
0
|
|
|
warn "DESTROY $_[0]->{path}\n" if DEBUG; |
1013
|
0
|
|
|
|
|
|
my $self = shift; |
1014
|
0
|
0
|
|
|
|
|
my $ioloop = $self->{_irc}{ioloop} or return; |
1015
|
0
|
0
|
|
|
|
|
my $keepnick_tid = $self->{keepnick_tid} or return; |
1016
|
0
|
|
|
|
|
|
$ioloop->remove($keepnick_tid); |
1017
|
|
|
|
|
|
|
} |
1018
|
|
|
|
|
|
|
|
1019
|
|
|
|
|
|
|
=head1 COPYRIGHT |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
See L. |
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
=head1 AUTHOR |
1024
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
Jan Henning Thorsen |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
Marcus Ramberg |
1028
|
|
|
|
|
|
|
|
1029
|
|
|
|
|
|
|
=cut |
1030
|
|
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
1; |