File Coverage

blib/lib/Mojo/IRC/Server.pm
Criterion Covered Total %
statement 39 412 9.4
branch 0 140 0.0
condition 0 19 0.0
subroutine 13 64 20.3
pod 1 21 4.7
total 53 656 8.0


line stmt bran cond sub pod time code
1             package Mojo::IRC::Server;
2 1     1   22063 use strict;
  1         2  
  1         42  
3             $Mojo::IRC::Server::VERSION = "1.7.3";
4 1     1   1004 use Encode;
  1         11850  
  1         87  
5 1     1   1050147 use Encode::Locale;
  1         6750  
  1         78  
6 1     1   10 use Carp;
  1         2  
  1         117  
7 1     1   1375 use Parse::IRC;
  1         3548  
  1         61  
8 1     1   807 use Mojo::IOLoop;
  1         633709  
  1         7  
9 1     1   43 use POSIX ();
  1         2  
  1         21  
10 1     1   5 use List::Util qw(first);
  1         2  
  1         62  
11 1     1   5 use Fcntl ':flock';
  1         2  
  1         141  
12 1     1   565 use Mojo::IRC::Server::Base 'Mojo::EventEmitter';
  1         2  
  1         7  
13 1     1   631 use Mojo::IRC::Server::User;
  1         3  
  1         12  
14 1     1   613 use Mojo::IRC::Server::Channel;
  1         3  
  1         9  
15              
16             has host => "0.0.0.0";
17             has port => 6667;
18             has listen => undef;
19             has network => "Mojo IRC NetWork";
20             has ioloop => sub { Mojo::IOLoop->singleton };
21             has parser => sub { Parse::IRC->new };
22             has servername => "mojo-irc-server";
23             has clienthost => undef,
24             has create_time => sub{POSIX::strftime( '%Y/%m/%d %H:%M:%S', localtime() )};
25             has log_level => "info";
26             has log_path => undef;
27              
28             has user => sub {[]};
29             has channel => sub {[]};
30              
31             has log => sub{
32             require Mojo::Log;
33 1     1   244 no warnings 'redefine';
  1         2  
  1         6558  
34             *Mojo::Log::append = sub{
35             my ($self, $msg) = @_;
36             return unless my $handle = $self->handle;
37             flock $handle, LOCK_EX;
38             $handle->print(encode("console_out", decode("utf8",$msg))) or $_[0]->die("Can't write to log: $!");
39             flock $handle, LOCK_UN;
40             };
41             Mojo::Log->new(path=>$_[0]->log_path,level=>$_[0]->log_level,format=>sub{
42             my ($time, $level, @lines) = @_;
43             my $title="";
44             if(ref $lines[0] eq "HASH"){
45             my $opt = shift @lines;
46             $time = $opt->{"time"} if defined $opt->{"time"};
47             $title = (defined $opt->{"title"})?$opt->{title} . " ":"";
48             $level = $opt->{level} if defined $opt->{"level"};
49             }
50             @lines = split /\n/,join "",@lines;
51             my $return = "";
52             $time = POSIX::strftime('[%y/%m/%d %H:%M:%S]',localtime($time));
53             for(@lines){
54             $return .=
55             $time
56             . " "
57             . "[$level]"
58             . " "
59             . $title
60             . $_
61             . "\n";
62             }
63             return $return;
64             });
65             };
66              
67             sub new_user{
68 0     0 0   my $s = shift;
69 0           my $user = $s->add_user(Mojo::IRC::Server::User->new(@_,_server=>$s));
70 0 0         return $user if $user->is_virtual;
71             $user->io->on(read=>sub{
72 0     0     my($stream,$bytes) = @_;
73 0           $bytes = $user->buffer . $bytes;
74 0           my $pos = rindex($bytes,"\r\n");
75 0           my $lines = substr($bytes,0,$pos);
76 0           my $remains = substr($bytes,$pos+2);
77 0           $user->buffer($remains);
78 0           $stream->emit(line=>$_) for split /\r\n/,$lines;
79 0           });
80             $user->io->on(line=>sub{
81 0     0     my($stream,$line) = @_;
82 0           my $msg = $s->parser->parse($line);
83 0           $user->last_active_time(time());
84 0           $s->emit(user_msg=>$user,$msg);
85 0 0         if($msg->{command} eq "PASS"){$user->emit(pass=>$msg)}
  0 0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
86 0           elsif($msg->{command} eq "NICK"){$user->emit(nick=>$msg);$s->emit(nick=>$user,$msg);}
  0            
87 0           elsif($msg->{command} eq "USER"){$user->emit(user=>$msg);$s->emit(user=>$user,$msg);}
  0            
88 0           elsif($msg->{command} eq "JOIN"){$user->emit(join=>$msg);$s->emit(join=>$user,$msg);}
  0            
89 0           elsif($msg->{command} eq "PART"){$user->emit(part=>$msg);$s->emit(part=>$user,$msg);}
  0            
90 0           elsif($msg->{command} eq "PING"){$user->emit(ping=>$msg);$s->emit(ping=>$user,$msg);}
  0            
91 0           elsif($msg->{command} eq "PONG"){$user->emit(pong=>$msg);$s->emit(pong=>$user,$msg);}
  0            
92 0           elsif($msg->{command} eq "MODE"){$user->emit(mode=>$msg);$s->emit(mode=>$user,$msg);}
  0            
93 0           elsif($msg->{command} eq "PRIVMSG"){$user->emit(privmsg=>$msg);$s->emit(privmsg=>$user,$msg);}
  0            
94 0           elsif($msg->{command} eq "QUIT"){$user->is_quit(1);$user->emit(quit=>$msg);$s->emit(quit=>$user,$msg);}
  0            
  0            
95 0           elsif($msg->{command} eq "WHO"){$user->emit(who=>$msg);$s->emit(who=>$user,$msg);}
  0            
96 0           elsif($msg->{command} eq "WHOIS"){$user->emit(whois=>$msg);$s->emit(whois=>$user,$msg);}
  0            
97 0           elsif($msg->{command} eq "LIST"){$user->emit(list=>$msg);$s->emit(list=>$user,$msg);}
  0            
98 0           elsif($msg->{command} eq "TOPIC"){$user->emit(topic=>$msg);$s->emit(topic=>$user,$msg);}
  0            
99 0           elsif($msg->{command} eq "AWAY"){$user->emit(away=>$msg);$s->emit(away=>$user,$msg);}
  0            
100 0           else{$user->send($user->serverident,"421",$user->nick,$msg->{command},"Unknown command");}
101 0           });
102              
103             $user->io->on(error=>sub{
104 0     0     my ($stream, $err) = @_;
105 0           $user->emit("close",$err);
106 0           $s->emit(close_user=>$user,$err);
107 0           $s->debug("C[" .$user->name."] 连接错误: $err");
108 0           });
109             $user->io->on(close=>sub{
110 0     0     my ($stream, $err) = @_;
111 0           $user->emit("close",$err);
112 0           $s->emit(close_user=>$user,$err);
113 0           });
114             $user->on(close=>sub{
115 0     0     my ($user,$err) = @_;
116 0 0         return if $user->is_quit;
117 0 0         my $quit_reason = defined $user->close_reason? $user->close_reason:
    0          
118             defined $err ? $err :
119             "remote host closed connection";
120 0           $user->forward($user->ident,"QUIT",$quit_reason);
121 0           $user->is_quit(1);
122 0           $user->info("[" . $user->name . "] 已退出($quit_reason)");
123 0           $user->{_server}->remove_user($user);
124 0           });
125 0     0     $user->on(pass=>sub{my($user,$msg) = @_;my $pass = $msg->{params}[0]; $user->pass($pass);});
  0            
  0            
  0            
126 0     0     $user->on(nick=>sub{my($user,$msg) = @_;my $nick = $msg->{params}[0];$user->set_nick($nick)});
  0            
  0            
  0            
127 0     0     $user->on(user=>sub{my($user,$msg) = @_;
128 0 0         if(defined $user->search_user(user=>$msg->{params}[0])){
129 0           $user->send($user->serverident,"446",$user->nick,"该帐号已被使用");
130 0           $user->io->close_gracefully();
131 0           $user->{_server}->remove_user($user);
132 0           return;
133             }
134 0           $user->user($msg->{params}[0]);
135             #$user->mode($msg->{params}[1]);
136 0           $user->realname($msg->{params}[3]);
137 0 0 0       if(!$user->is_registered and $user->nick ne "*" and $user->user ne "*"){
      0        
138 0           $user->is_registered(1);
139 0           $user->send($user->serverident,"001",$user->nick,"欢迎来到 Mojo IRC Network " . $user->ident);
140 0           $user->send($user->serverident,"396",$user->nick,$user->host,"您的主机地址已被隐藏");
141             }
142 0           });
143 0     0     $user->on(join=>sub{my($user,$msg) = @_;
144 0           my $channels = $msg->{params}[0];
145 0           for my $channel_name (split /,/,$channels){
146 0           my $channel = $user->search_channel(name=>$channel_name);
147 0 0         if(defined $channel){
148 0           $user->join_channel($channel);
149             }
150             else{
151 0           $channel = $user->new_channel(name=>$channel_name,id=>lc($channel_name));
152 0           $user->join_channel($channel);
153             }
154             }
155 0           });
156 0     0     $user->on(part=>sub{my($user,$msg) = @_;
157 0           my $channel_name = $msg->{params}[0];
158 0           my $part_info = $msg->{params}[1];
159 0           my $channel = $user->search_channel(name=>$channel_name);
160 0 0         return if not defined $channel;
161 0           $user->part_channel($channel,$part_info);
162 0           });
163 0     0     $user->on(ping=>sub{my($user,$msg) = @_;
164 0           my $servername = $msg->{params}[0];
165 0           $user->send($user->serverident,"PONG",$user->servername,$servername);
166 0           });
167             $user->on(pong=>sub{
168 0     0     my($user,$msg) = @_;
169 0           my $current_ping_count = $user->ping_count;
170 0           $user->ping_count(--$current_ping_count);
171 0           });
172 0     0     $user->on(quit=>sub{my($user,$msg) = @_;
173 0           my $quit_reason = $msg->{params}[0];
174 0           $user->quit($quit_reason);
175 0           });
176 0     0     $user->on(privmsg=>sub{my($user,$msg) = @_;
177 0           $user->last_speak_time(time());
178 0 0         if(substr($msg->{params}[0],0,1) eq "#" ){
179 0           my $channel_name = $msg->{params}[0];
180 0           my $content = $msg->{params}[1];
181 0           my $channel = $user->search_channel(name=>$channel_name);
182 0 0         if(not defined $channel){$user->send($user->serverident,"403",$user->nick,$channel_name,"No such channel");return}
  0            
  0            
183 0           $channel->forward($user,$user->ident,"PRIVMSG",$channel_name,$content);
184 0           $s->info({level=>"IRC频道消息",title=>$user->nick ."|" .$channel->name.":"},$content);
185             }
186             else{
187 0           my $nick = $msg->{params}[0];
188 0           my $content = $msg->{params}[1];
189 0           my $u = $user->search_user(nick=>$nick);
190 0 0         if(defined $u){
191 0           $u->send($user->ident,"PRIVMSG",$nick,$content);
192 0 0         $user->send($user->serverident,"301",$user->nick,$u->nick,$u->away_info) if $u->is_away;
193 0           $s->info({level=>"IRC私信消息",title=>"[".$user->nick."]->[$nick] :"},$content);
194             }
195             else{
196 0           $user->send($user->serverident,"401",$user->nick,$nick,"No such nick");
197             }
198             }
199 0           });
200 0     0     $user->on(mode=>sub{my($user,$msg) = @_;
201 0 0         if(substr($msg->{params}[0],0,1) eq "#" ){
202 0           my $channel_name = $msg->{params}[0];
203 0           my $channel_mode = $msg->{params}[1];
204 0           my $channel = $user->search_channel(name=>$channel_name);
205 0 0         if(not defined $channel){$user->send($user->serverident,"403",$user->nick,$channel_name,"No such channel");return}
  0            
  0            
206 0 0 0       if(defined $channel_mode and $channel_mode eq "b"){
    0 0        
207 0           $user->send($user->serverident,"368",$user->nick,$channel_name,"End of channel ban list");
208             }
209             elsif(defined $channel_mode and $channel_mode ne "b") {
210 0           $channel->set_mode($user,$channel_mode);
211             }
212             else{
213 0           $user->send($user->serverident,"324",$user->nick,$channel_name,'+'.$channel->mode);
214 0           $user->send($user->serverident,"329",$user->nick,$channel_name,$channel->ctime);
215             }
216             }
217             else{
218 0           my $nick = $msg->{params}[0];
219 0           my $mode = $msg->{params}[1];
220 0 0         if(defined $mode){$user->set_mode($mode)}
  0            
221 0           else{$user->send($user->serverident,"221",$user->nick,'+'.$user->mode)}
222             }
223 0           });
224 0     0     $user->on(who=>sub{my($user,$msg) = @_;
225 0 0         if(substr($msg->{params}[0],0,1) eq "#" ){
226 0           my $channel_name = $msg->{params}[0];
227 0           my $channel = $user->search_channel(name=>$channel_name);
228 0 0         if(not defined $channel){$user->send($user->serverident,"403",$user->nick,$channel_name,"No such channel");return}
  0            
  0            
229 0           for($channel->users){
230 0           $user->send($user->serverident,"352",$user->nick,$channel_name,$_->user,$_->host,$_->servername,$_->nick,"H","0 " . $_->realname);
231             }
232 0           $user->send($user->serverident,"315",$user->nick,$channel_name,"End of WHO list");
233             }
234             else{
235 0           my $nick = $msg->{params}[0];
236 0           my $u = $user->search_user(nick=>$nick);
237 0 0         if(defined $u){
238 0           my $channel_name = "*";
239 0 0         if($u->is_join_channel()){
240 0           my $last_channel = (grep {$_->mode !~ /s/} $u->channels)[-1];
  0            
241 0 0         $channel_name = $last_channel->name if defined $last_channel;
242             }
243 0           $user->send($user->serverident,"352",$user->nick,$channel_name,$u->user,$u->host,$u->servername,$u->nick,"H","0 " . $u->realname);
244 0           $user->send($user->serverident,"315",$user->nick,$nick,"End of WHO list");
245             }
246             else{
247 0           $user->send($user->serverident,"401",$user->nick,$nick,"No such nick");
248             }
249            
250             }
251 0           });
252 0     0     $user->on(whois=>sub{my($user,$msg) = @_;});
  0            
253 0     0     $user->on(list=>sub{my($user,$msg) = @_;
254 0           for my $channel ($user->{_server}->channels){
255 0 0         next if $channel->mode =~ /s/;
256 0           $user->send($user->serverident,"322",$user->nick,$channel->name,$channel->count(),$channel->topic);
257             }
258 0           $user->send($user->serverident,"323",$user->nick,"End of LIST");
259 0           });
260 0     0     $user->on(topic=>sub{my($user,$msg) = @_;
261 0           my $channel_name = $msg->{params}[0];
262 0           my $channel = $user->search_channel(name=>$channel_name);
263 0 0         if(not defined $channel){$user->send($user->serverident,"403",$user->nick,$channel_name,"No such channel");return}
  0            
  0            
264 0 0         if(defined $msg->{params}[1]){
265 0           my $topic = $msg->{params}[1];
266 0           $channel->set_topic($user,$topic);
267             }
268             else{
269 0           $user->send($user->serverident,"332",$user->nick,$channel_name,$channel->topic);
270             }
271 0           });
272 0     0     $user->on(away=>sub{my($user,$msg) = @_;
273 0 0         if($msg->{params}[0]){
274 0           my $away_info = $msg->{params}[0];
275 0           $user->away($away_info);
276             }
277             else{
278 0           $user->back();
279             }
280 0           });
281              
282 0           $user;
283             }
284             sub new_channel{
285 0     0 0   my $s = shift;
286 0           $s->add_channel(Mojo::IRC::Server::Channel->new(@_,_server=>$s));
287             }
288             sub add_channel{
289 0     0 0   my $s = shift;
290 0           my $channel = shift;
291 0           my $is_cover = shift;
292 0           my $channel_name = $channel->name;
293 0 0         $channel_name = "#" . $channel_name if substr($channel_name,0,1) ne "#";
294 0           $channel_name=~s/\s|,|&//g;
295 0           $channel->name($channel_name);
296 0           my $c = $s->search_channel(name=>$channel->name);
297 0 0         return $c if defined $c;
298 0           $c = $s->search_channel(id=>$channel->id);
299 0 0         if(defined $c){if($is_cover){$s->info("频道 " . $c->name. " 已更新");$c=$channel;};return $c;}
  0 0          
  0            
  0            
  0            
300 0           else{push @{$s->channel},$channel;$s->info("频道 ".$channel->name. " 已创建");return $channel;}
  0            
  0            
  0            
301              
302             }
303             sub add_user{
304 0     0 0   my $s = shift;
305 0           my $user = shift;
306 0           my $is_cover = shift;
307 0 0         if($user->is_virtual){
308 0           my $nick = $user->nick;
309 0 0         $nick =~s/\s|\@|!//g;$nick = '未知昵称' if not $nick;
  0            
310 0           $user->nick($nick);
311 0           my $u = $s->search_user(nick=>$user->nick,virtual=>1,id=>$user->id);
312 0 0         return $u if defined $u;
313 0           while(1){
314 0           my $u = $s->search_user(nick=>$user->nick);
315 0 0         if(defined $u){
316 0 0         if($nick =~/\((\d+)\)$/){
317 0           my $num = $1;$num++;$user->nick($nick . "($num)");
  0            
  0            
318             }
319 0           else{$user->nick($nick . "(1)")}
320             #$user->send($user->ident,"433",$user->nick,$nick,'昵称已经被使用')
321             }
322 0           else{last};
323             }
324             }
325 0           my $u = $s->search_user(id=>$user->id);
326 0 0         if(defined $u){if($is_cover){$s->info("C[".$u->name. "]已更新");$u=$user;};return $u;}
  0 0          
  0            
  0            
  0            
327             else{
328 0           push @{$s->user},$user;$s->info("C[".$user->name. "]已加入");return $user;
  0            
  0            
  0            
329             }
330             }
331             sub remove_user{
332 0     0 0   my $s = shift;
333 0           my $user = shift;
334 0           for(my $i=0;$i<@{$s->user};$i++){
  0            
335 0 0         if($user->id eq $s->user->[$i]->id){
336 0           $_->remove_user($s->user->[$i]->id) for $s->user->[$i]->channels;
337 0           $user->channel([]);
338 0           splice @{$s->user},$i,1;
  0            
339 0 0         if($user->is_virtual){
340 0           $s->info("c[".$user->name."] 已被移除");
341             }
342             else{
343 0           $s->info("C[".$user->name."] 已离开");
344             }
345 0           last;
346             }
347             }
348             }
349              
350             sub remove_channel{
351 0     0 0   my $s = shift;
352 0           my $channel = shift;
353 0           for(my $i=0;$i<@{$s->channel};$i++){
  0            
354 0 0         if($channel->id eq $s->channel->[$i]->id){
355 0           splice @{$s->channel},$i,1;
  0            
356 0           $s->info("频道 ".$channel->name." 已删除");
357 0           last;
358             }
359             }
360             }
361             sub users {
362 0     0 0   my $s = shift;
363 0           return @{$s->user};
  0            
364             }
365             sub channels{
366 0     0 0   my $s = shift;
367 0           return @{$s->channel};
  0            
368             }
369              
370             sub search_user{
371 0     0 0   my $s = shift;
372 0           my %p = @_;
373 0 0         return if 0 == grep {defined $p{$_}} keys %p;
  0            
374 0 0         if(wantarray){
375 0 0   0     return grep {my $c = $_;(first {$p{$_} ne $c->$_} grep {defined $p{$_}} keys %p) ? 0 : 1;} @{$s->user};
  0            
  0            
  0            
  0            
  0            
376             }
377             else{
378 0 0   0     return first {my $c = $_;(first {$p{$_} ne $c->$_} grep {defined $p{$_}} keys %p) ? 0 : 1;} @{$s->user};
  0            
  0            
  0            
  0            
  0            
379             }
380              
381             }
382             sub search_channel{
383 0     0 0   my $s = shift;
384 0           my %p = @_;
385 0 0         return if 0 == grep {defined $p{$_}} keys %p;
  0            
386 0 0         if(wantarray){
387 0 0   0     return grep {my $c = $_;(first {$_ eq "name"?(lc($p{$_}) ne lc($c->$_)):($p{$_} ne $c->$_)} grep {defined $p{$_}} keys %p) ? 0 : 1;} @{$s->channel};
  0 0          
  0            
  0            
  0            
  0            
388             }
389             else{
390 0 0   0     return first {my $c = $_;(first {$_ eq "name"?(lc($p{$_}) ne lc($c->$_)):($p{$_} ne $c->$_)} grep {defined $p{$_}} keys %p) ? 0 : 1;} @{$s->channel};
  0 0          
  0            
  0            
  0            
  0            
391             }
392              
393             }
394             sub timer{
395 0     0 0   my $s = shift;
396 0           $s->ioloop->timer(@_);
397             }
398             sub interval{
399 0     0 0   my $s = shift;
400 0           $s->ioloop->recurring(@_);
401             }
402             sub ident {
403 0     0 0   return $_[0]->servername;
404             }
405             sub ready {
406 0     0 0   my $s = shift;
407 0           my @listen = ();
408 0 0 0       if(defined $s->listen and ref $s->listen eq "ARRAY"){
409 0   0       push @listen,{host=>$_->{host} || "0.0.0.0",port=>$_->{port}||"6667"} for @{$s->listen} ;
  0   0        
410             }
411             else{
412 0           @listen = ({host=>$s->host,port=>$s->port});
413             }
414 0           for my $listen (@listen){
415             $s->ioloop->server({host=>$listen->{host},port=>$listen->{port}}=>sub{
416 0     0     my ($loop, $stream) = @_;
417 0           $stream->timeout(0);
418 0           my $id = join ":",(
419             $stream->handle->sockhost,
420             $stream->handle->sockport,
421             $stream->handle->peerhost,
422             $stream->handle->peerport
423             );
424 0           my $user = $s->new_user(
425             id => $id,
426             name => join(":",($stream->handle->peerhost,$stream->handle->peerport)),
427             io => $stream,
428             );
429 0 0         $user->host($s->clienthost) if defined $s->clienthost;
430 0           $s->emit(new_user=>$user);
431 0           });
432             }
433            
434             $s->on(new_user=>sub{
435 0     0     my ($s,$user)=@_;
436 0           $s->debug("C[".$user->name. "]已连接");
437 0           });
438              
439             $s->on(user_msg=>sub{
440 0     0     my ($s,$user,$msg)=@_;
441 0           $s->debug("C[".$user->name."] $msg->{raw_line}");
442 0           });
443              
444             $s->on(close_user=>sub{
445 0     0     my ($s,$user,$msg)=@_;
446 0           });
447              
448             $s->interval(60,sub{
449 0 0   0     for(grep {defined $_->last_active_time and time() - $_->last_active_time > 60 } grep {!$_->is_virtual} $s->users){
  0            
  0            
450 0 0         if($_->ping_count >=3 ){
451 0           $_->close_reason("PING timeout 180 seconds");
452 0           $_->io->close_gracefully();
453             }
454             else{
455 0           $_->send(undef,"PING",$_->servername);
456 0           my $current_ping_count = $_->ping_count;
457 0           $_->ping_count(++$current_ping_count);
458             }
459             }
460 0           });
461             }
462             sub run{
463 0     0 0   my $s = shift;
464 0           $s->ready();
465 0 0         $s->ioloop->start unless $s->ioloop->is_running;
466             }
467             sub die{
468 0     0 0   my $s = shift;
469 0     0     local $SIG{__DIE__} = sub{$s->log->fatal(@_);exit -1};
  0            
  0            
470 0           Carp::confess(@_);
471             }
472             sub info{
473 0     0 0   my $s = shift;
474 0           $s->log->info(@_);
475 0           $s;
476             }
477             sub warn{
478 0     0 0   my $s = shift;
479 0           $s->log->warn(@_);
480 0           $s;
481             }
482             sub error{
483 0     0 1   my $s = shift;
484 0           $s->log->error(@_);
485 0           $s;
486             }
487             sub fatal{
488 0     0 0   my $s = shift;
489 0           $s->log->fatal(@_);
490 0           $s;
491             }
492             sub debug{
493 0     0 0   my $s = shift;
494 0           $s->log->debug(@_);
495 0           $s;
496             }
497              
498              
499             1;