| 1 | package App::Mobirc::Plugin::Component::IRCClient; |
|---|
| 2 | use strict; |
|---|
| 3 | use warnings; |
|---|
| 4 | |
|---|
| 5 | use POE; |
|---|
| 6 | use POE::Sugar::Args; |
|---|
| 7 | use POE::Component::IRC; |
|---|
| 8 | |
|---|
| 9 | use Encode; |
|---|
| 10 | use Carp; |
|---|
| 11 | |
|---|
| 12 | use App::Mobirc::Model::Message; |
|---|
| 13 | use App::Mobirc::Util; |
|---|
| 14 | |
|---|
| 15 | sub register { |
|---|
| 16 | my ($class, $global_context, $conf) = @_; |
|---|
| 17 | |
|---|
| 18 | DEBUG "register ircclient component"; |
|---|
| 19 | $conf->{ping_delay} ||= 30; |
|---|
| 20 | $conf->{reconnect_delay} ||= 10; |
|---|
| 21 | |
|---|
| 22 | $global_context->register_hook( |
|---|
| 23 | 'run_component' => sub { _init($conf, shift) }, |
|---|
| 24 | ); |
|---|
| 25 | $global_context->register_hook( |
|---|
| 26 | 'process_command' => sub { my ($global_context, $command, $channel) = @_; _process_command($conf, $global_context, $command, $channel) }, |
|---|
| 27 | ); |
|---|
| 28 | } |
|---|
| 29 | |
|---|
| 30 | sub _process_command { |
|---|
| 31 | my ($conf, $global_context, $command, $channel) = @_; |
|---|
| 32 | |
|---|
| 33 | my $irc_incode = $conf->{incode}; |
|---|
| 34 | if ($command && $channel->name =~ /^[#*%]/) { |
|---|
| 35 | if ($command =~ m{^/}) { |
|---|
| 36 | DEBUG "SENDING COMMAND"; |
|---|
| 37 | $command =~ s!^/!!g; |
|---|
| 38 | |
|---|
| 39 | my @args = |
|---|
| 40 | map { encode( $irc_incode, $_ ) } split /\s+/, |
|---|
| 41 | $command; |
|---|
| 42 | |
|---|
| 43 | $poe_kernel->post('mobirc_irc', @args); |
|---|
| 44 | } else { |
|---|
| 45 | DEBUG "NORMAL PRIVMSG"; |
|---|
| 46 | |
|---|
| 47 | $poe_kernel->post( 'mobirc_irc', |
|---|
| 48 | privmsg => encode( $irc_incode, $channel->name ) => |
|---|
| 49 | encode( $irc_incode, $command ) ); |
|---|
| 50 | |
|---|
| 51 | DEBUG "Sending command $command"; |
|---|
| 52 | # FIXME: httpd $B4X78$J$$7o(B |
|---|
| 53 | if ($global_context->config->{httpd}->{echo} eq true) { |
|---|
| 54 | $channel->add_message( |
|---|
| 55 | App::Mobirc::Model::Message->new( |
|---|
| 56 | who => decode( |
|---|
| 57 | $irc_incode, |
|---|
| 58 | $poe_kernel->alias_resolve('irc_session')->get_heap->{irc}->nick_name |
|---|
| 59 | ), |
|---|
| 60 | body => $command, |
|---|
| 61 | class => 'public', |
|---|
| 62 | ) |
|---|
| 63 | ); |
|---|
| 64 | } |
|---|
| 65 | } |
|---|
| 66 | return true; |
|---|
| 67 | } |
|---|
| 68 | return false; |
|---|
| 69 | } |
|---|
| 70 | |
|---|
| 71 | sub _init { |
|---|
| 72 | my ($config, $global_context) = @_; |
|---|
| 73 | |
|---|
| 74 | DEBUG "initialize ircclient"; |
|---|
| 75 | # irc component |
|---|
| 76 | my $irc = POE::Component::IRC->spawn( |
|---|
| 77 | Alias => 'mobirc_irc', |
|---|
| 78 | Nick => $config->{nick}, |
|---|
| 79 | Username => $config->{username}, |
|---|
| 80 | Ircname => $config->{desc}, |
|---|
| 81 | Server => $config->{server}, |
|---|
| 82 | Port => $config->{port}, |
|---|
| 83 | Password => $config->{password} |
|---|
| 84 | ); |
|---|
| 85 | |
|---|
| 86 | POE::Session->create( |
|---|
| 87 | heap => { |
|---|
| 88 | seen_traffic => false, |
|---|
| 89 | disconnect_msg => true, |
|---|
| 90 | config => $config, |
|---|
| 91 | irc => $irc, |
|---|
| 92 | global_context => $global_context, |
|---|
| 93 | }, |
|---|
| 94 | inline_states => { |
|---|
| 95 | _start => \&on_irc_start, |
|---|
| 96 | _default => \&on_irc_default, |
|---|
| 97 | |
|---|
| 98 | irc_001 => \&on_irc_001, |
|---|
| 99 | irc_join => \&on_irc_join, |
|---|
| 100 | irc_part => \&on_irc_part, |
|---|
| 101 | irc_public => \&on_irc_public, |
|---|
| 102 | irc_notice => \&on_irc_notice, |
|---|
| 103 | irc_topic => \&on_irc_topic, |
|---|
| 104 | irc_332 => \&on_irc_topicraw, |
|---|
| 105 | irc_ctcp_action => \&on_irc_ctcp_action, |
|---|
| 106 | irc_kick => \&on_irc_kick, |
|---|
| 107 | irc_snotice => \&on_irc_snotice, |
|---|
| 108 | |
|---|
| 109 | autoping => \&do_autoping, |
|---|
| 110 | connect => \&do_connect, |
|---|
| 111 | |
|---|
| 112 | irc_disconnected => \&on_irc_reconnect, |
|---|
| 113 | irc_error => \&on_irc_reconnect, |
|---|
| 114 | irc_socketerr => \&on_irc_reconnect, |
|---|
| 115 | } |
|---|
| 116 | ); |
|---|
| 117 | |
|---|
| 118 | $global_context->add_channel( |
|---|
| 119 | App::Mobirc::Model::Channel->new($global_context, U('*server*')) |
|---|
| 120 | ); |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | # ------------------------------------------------------------------------- |
|---|
| 124 | |
|---|
| 125 | sub on_irc_default { |
|---|
| 126 | DEBUG "ignore unknown event: $_[ARG0]"; |
|---|
| 127 | } |
|---|
| 128 | |
|---|
| 129 | sub on_irc_start { |
|---|
| 130 | my $poe = sweet_args; |
|---|
| 131 | DEBUG "START"; |
|---|
| 132 | |
|---|
| 133 | $poe->kernel->alias_set('irc_session'); |
|---|
| 134 | |
|---|
| 135 | DEBUG "input charset is: " . $poe->heap->{config}->{incode}; |
|---|
| 136 | |
|---|
| 137 | $poe->heap->{irc}->yield( register => 'all' ); |
|---|
| 138 | $poe->heap->{irc}->yield( connect => {} ); |
|---|
| 139 | } |
|---|
| 140 | |
|---|
| 141 | sub on_irc_001 { |
|---|
| 142 | my $poe = sweet_args; |
|---|
| 143 | |
|---|
| 144 | DEBUG "CONNECTED"; |
|---|
| 145 | |
|---|
| 146 | my $channel = $poe->heap->{global_context}->get_channel(decode( 'utf8', '*server*' )); |
|---|
| 147 | $channel->add_message( |
|---|
| 148 | App::Mobirc::Model::Message->new( |
|---|
| 149 | who => undef, |
|---|
| 150 | body => decode('utf8', 'Connected to irc server!'), |
|---|
| 151 | class => 'connect', |
|---|
| 152 | ) |
|---|
| 153 | ); |
|---|
| 154 | |
|---|
| 155 | $poe->heap->{disconnect_msg} = true; |
|---|
| 156 | $poe->kernel->delay( autoping => $poe->heap->{config}->{ping_delay} ); |
|---|
| 157 | } |
|---|
| 158 | |
|---|
| 159 | sub on_irc_join { |
|---|
| 160 | my $poe = sweet_args; |
|---|
| 161 | |
|---|
| 162 | DEBUG "JOIN"; |
|---|
| 163 | |
|---|
| 164 | my ($who, $channel_name) = _get_args($poe); |
|---|
| 165 | |
|---|
| 166 | $who =~ s/!.*//; |
|---|
| 167 | |
|---|
| 168 | # chop off after the gap (bug workaround of madoka) |
|---|
| 169 | $channel_name =~ s/ .*//; |
|---|
| 170 | $channel_name = normalize_channel_name($channel_name); |
|---|
| 171 | |
|---|
| 172 | # create channel |
|---|
| 173 | my $channel = $poe->heap->{global_context}->get_channel($channel_name); |
|---|
| 174 | unless ($channel) { |
|---|
| 175 | $channel = App::Mobirc::Model::Channel->new( |
|---|
| 176 | $poe->heap->{global_context}, |
|---|
| 177 | $channel_name, |
|---|
| 178 | ); |
|---|
| 179 | $poe->heap->{global_context}->add_channel( $channel ); |
|---|
| 180 | } |
|---|
| 181 | |
|---|
| 182 | my $irc = $poe->heap->{irc}; |
|---|
| 183 | unless ( $who eq $irc->nick_name ) { |
|---|
| 184 | $channel->add_message( |
|---|
| 185 | App::Mobirc::Model::Message->new( |
|---|
| 186 | who => undef, |
|---|
| 187 | body => $who . U(" joined"), |
|---|
| 188 | class => 'join', |
|---|
| 189 | ) |
|---|
| 190 | ); |
|---|
| 191 | } |
|---|
| 192 | $poe->heap->{seen_traffic} = true; |
|---|
| 193 | $poe->heap->{disconnect_msg} = true; |
|---|
| 194 | } |
|---|
| 195 | |
|---|
| 196 | sub on_irc_part { |
|---|
| 197 | my $poe = sweet_args; |
|---|
| 198 | |
|---|
| 199 | my ($who, $channel_name, $msg) = _get_args($poe); |
|---|
| 200 | |
|---|
| 201 | $who =~ s/!.*//; |
|---|
| 202 | |
|---|
| 203 | # chop off after the gap (bug workaround of POE::Filter::IRC) |
|---|
| 204 | $channel_name =~ s/ .*//; |
|---|
| 205 | $channel_name = normalize_channel_name($channel_name); |
|---|
| 206 | |
|---|
| 207 | my $irc = $poe->heap->{irc}; |
|---|
| 208 | if ( $who eq $irc->nick_name ) { |
|---|
| 209 | $poe->heap->{global_context}->delete_channel($channel_name); |
|---|
| 210 | } |
|---|
| 211 | else { |
|---|
| 212 | my $message = "$who leaves"; |
|---|
| 213 | if ($msg) { |
|---|
| 214 | $message .= "($msg)"; |
|---|
| 215 | } |
|---|
| 216 | |
|---|
| 217 | my $channel = $poe->heap->{global_context}->get_channel($channel_name); |
|---|
| 218 | $channel->add_message( |
|---|
| 219 | App::Mobirc::Model::Message->new( |
|---|
| 220 | who => undef, |
|---|
| 221 | body => $message, |
|---|
| 222 | class => 'leave', |
|---|
| 223 | ) |
|---|
| 224 | ); |
|---|
| 225 | } |
|---|
| 226 | $poe->heap->{seen_traffic} = true; |
|---|
| 227 | $poe->heap->{disconnect_msg} = true; |
|---|
| 228 | } |
|---|
| 229 | |
|---|
| 230 | sub on_irc_public { |
|---|
| 231 | my $poe = sweet_args; |
|---|
| 232 | |
|---|
| 233 | DEBUG "IRC PUBLIC"; |
|---|
| 234 | |
|---|
| 235 | my ($who, $channel_name, $msg) = _get_args($poe); |
|---|
| 236 | |
|---|
| 237 | $who =~ s/!.*//; |
|---|
| 238 | |
|---|
| 239 | $channel_name = $channel_name->[0]; |
|---|
| 240 | $channel_name = normalize_channel_name($channel_name); |
|---|
| 241 | |
|---|
| 242 | my $channel = $poe->heap->{global_context}->get_channel($channel_name); |
|---|
| 243 | $channel->add_message( |
|---|
| 244 | App::Mobirc::Model::Message->new( |
|---|
| 245 | who => $who, |
|---|
| 246 | body => $msg, |
|---|
| 247 | class => 'public', |
|---|
| 248 | ) |
|---|
| 249 | ); |
|---|
| 250 | my $irc = $poe->heap->{irc}; |
|---|
| 251 | if ( $who eq $irc->nick_name ) { |
|---|
| 252 | DEBUG "CLEAR UNREAD"; |
|---|
| 253 | $channel->clear_unread; |
|---|
| 254 | } |
|---|
| 255 | |
|---|
| 256 | $poe->heap->{seen_traffic} = true; |
|---|
| 257 | $poe->heap->{disconnect_msg} = true; |
|---|
| 258 | } |
|---|
| 259 | |
|---|
| 260 | sub on_irc_notice { |
|---|
| 261 | my $poe = sweet_args; |
|---|
| 262 | |
|---|
| 263 | my ($who, $channel_name, $msg) = _get_args($poe); |
|---|
| 264 | |
|---|
| 265 | DEBUG "IRC NOTICE $who $channel_name $msg"; |
|---|
| 266 | |
|---|
| 267 | for my $code (@{ $poe->heap->{global_context}->get_hook_codes('on_irc_notice') }) { |
|---|
| 268 | my $finished = $code->($poe, $who, $channel_name, $msg); |
|---|
| 269 | return if $finished; |
|---|
| 270 | } |
|---|
| 271 | |
|---|
| 272 | $who =~ s/!.*//; |
|---|
| 273 | $channel_name = $channel_name->[0]; |
|---|
| 274 | $channel_name = normalize_channel_name($channel_name); |
|---|
| 275 | |
|---|
| 276 | my $channel = $poe->heap->{global_context}->get_channel($channel_name); |
|---|
| 277 | $channel->add_message( |
|---|
| 278 | App::Mobirc::Model::Message->new( |
|---|
| 279 | who => $who, |
|---|
| 280 | body => $msg, |
|---|
| 281 | class => 'notice', |
|---|
| 282 | ) |
|---|
| 283 | ); |
|---|
| 284 | $poe->heap->{seen_traffic} = true; |
|---|
| 285 | $poe->heap->{disconnect_msg} = true; |
|---|
| 286 | } |
|---|
| 287 | |
|---|
| 288 | sub on_irc_topic { |
|---|
| 289 | my $poe = sweet_args; |
|---|
| 290 | |
|---|
| 291 | my ($who, $channel_name, $topic) = _get_args($poe); |
|---|
| 292 | |
|---|
| 293 | $who =~ s/!.*//; |
|---|
| 294 | |
|---|
| 295 | DEBUG "SET TOPIC"; |
|---|
| 296 | $channel_name = normalize_channel_name($channel_name); |
|---|
| 297 | |
|---|
| 298 | my $channel = $poe->heap->{global_context}->get_channel($channel_name); |
|---|
| 299 | $channel->topic($topic); |
|---|
| 300 | $channel->add_message( |
|---|
| 301 | App::Mobirc::Model::Message->new( |
|---|
| 302 | who => undef, |
|---|
| 303 | body => "$who set topic: $topic", |
|---|
| 304 | class => 'topic', |
|---|
| 305 | ) |
|---|
| 306 | ); |
|---|
| 307 | |
|---|
| 308 | $poe->heap->{seen_traffic} = true; |
|---|
| 309 | $poe->heap->{disconnect_msg} = true; |
|---|
| 310 | } |
|---|
| 311 | |
|---|
| 312 | sub on_irc_topicraw { |
|---|
| 313 | my $poe = sweet_args; |
|---|
| 314 | |
|---|
| 315 | my ($x, $y, $dat) = _get_args($poe); |
|---|
| 316 | |
|---|
| 317 | my ( $channel, $topic ) = @{$dat}; |
|---|
| 318 | |
|---|
| 319 | DEBUG "SET TOPIC RAW: $channel => $topic"; |
|---|
| 320 | |
|---|
| 321 | $poe->heap->{global_context}->get_channel(normalize_channel_name($channel))->topic($topic); |
|---|
| 322 | $poe->heap->{seen_traffic} = true; |
|---|
| 323 | $poe->heap->{disconnect_msg} = true; |
|---|
| 324 | } |
|---|
| 325 | |
|---|
| 326 | sub on_irc_ctcp_action { |
|---|
| 327 | my $poe = sweet_args; |
|---|
| 328 | |
|---|
| 329 | my ($who, $channel_name, $msg) = _get_args($poe); |
|---|
| 330 | |
|---|
| 331 | $who =~ s/!.*//; |
|---|
| 332 | $channel_name = $channel_name->[0]; |
|---|
| 333 | |
|---|
| 334 | my $channel = $poe->heap->{global_context}->get_channel($channel_name); |
|---|
| 335 | my $body = sprintf(decode('utf8', "* %s %s"), $who, $msg); |
|---|
| 336 | $channel->add_message( |
|---|
| 337 | App::Mobirc::Model::Message->new( |
|---|
| 338 | who => undef, |
|---|
| 339 | body => $body, |
|---|
| 340 | class => 'ctcp_action', |
|---|
| 341 | ) |
|---|
| 342 | ); |
|---|
| 343 | |
|---|
| 344 | $poe->heap->{seen_traffic} = true; |
|---|
| 345 | $poe->heap->{disconnect_msg} = true; |
|---|
| 346 | } |
|---|
| 347 | |
|---|
| 348 | sub on_irc_kick { |
|---|
| 349 | my $poe = sweet_args; |
|---|
| 350 | |
|---|
| 351 | DEBUG "DNBKICK"; |
|---|
| 352 | |
|---|
| 353 | my ($kicker, $channel_name, $kickee, $msg) = _get_args($poe); |
|---|
| 354 | $msg ||= 'Flooder'; |
|---|
| 355 | |
|---|
| 356 | $kicker =~ s/!.*//; |
|---|
| 357 | |
|---|
| 358 | $poe->heap->{global_context}->get_channel($channel_name)->add_message( |
|---|
| 359 | App::Mobirc::Model::Message->new( |
|---|
| 360 | who => undef, |
|---|
| 361 | body => "$kicker has kicked $kickee($msg)", |
|---|
| 362 | class => 'kick', |
|---|
| 363 | ) |
|---|
| 364 | ); |
|---|
| 365 | |
|---|
| 366 | $poe->heap->{seen_traffic} = true; |
|---|
| 367 | $poe->heap->{disconnect_msg} = true; |
|---|
| 368 | } |
|---|
| 369 | |
|---|
| 370 | sub do_connect { |
|---|
| 371 | my $poe = sweet_args; |
|---|
| 372 | |
|---|
| 373 | $poe->heap->{irc}->yield( connect => {} ); |
|---|
| 374 | } |
|---|
| 375 | |
|---|
| 376 | sub do_autoping { |
|---|
| 377 | my $poe = sweet_args; |
|---|
| 378 | |
|---|
| 379 | $poe->kernel->post( mobirc_irc => time ) unless $poe->heap->{seen_traffic}; |
|---|
| 380 | $poe->heap->{seen_traffic} = false; |
|---|
| 381 | $poe->kernel->delay( autoping => $poe->heap->{config}->{ping_delay} ); |
|---|
| 382 | } |
|---|
| 383 | |
|---|
| 384 | sub on_irc_snotice { |
|---|
| 385 | my $poe = sweet_args; |
|---|
| 386 | |
|---|
| 387 | my ($message, ) = _get_args($poe); |
|---|
| 388 | |
|---|
| 389 | DEBUG "getting snotice : $message"; |
|---|
| 390 | |
|---|
| 391 | my $channel = $poe->heap->{global_context}->get_channel(U('*server*' )); |
|---|
| 392 | $channel->add_message( |
|---|
| 393 | App::Mobirc::Model::Message->new( |
|---|
| 394 | who => undef, |
|---|
| 395 | body => $message, |
|---|
| 396 | class => 'snotice', |
|---|
| 397 | ) |
|---|
| 398 | ); |
|---|
| 399 | } |
|---|
| 400 | |
|---|
| 401 | sub on_irc_reconnect { |
|---|
| 402 | my $poe = sweet_args; |
|---|
| 403 | |
|---|
| 404 | DEBUG "!RECONNECT! " . $poe->heap->{disconnect_msg}; |
|---|
| 405 | if ( $poe->heap->{disconnect_msg} ) { |
|---|
| 406 | my $channel = $poe->heap->{global_context}->get_channel(decode( 'utf8', '*server*' )); |
|---|
| 407 | $channel->add_message( |
|---|
| 408 | App::Mobirc::Model::Message->new( |
|---|
| 409 | who => undef, |
|---|
| 410 | body => decode( 'utf8', 'Disconnected from irc server, trying to reconnect...'), |
|---|
| 411 | class => 'reconnect', |
|---|
| 412 | ) |
|---|
| 413 | ); |
|---|
| 414 | } |
|---|
| 415 | $poe->heap->{disconnect_msg} = false; |
|---|
| 416 | $poe->kernel->delay( connect => $poe->heap->{config}->{reconnect_delay} ); |
|---|
| 417 | } |
|---|
| 418 | |
|---|
| 419 | # FIXME: I want more cool implement |
|---|
| 420 | sub _get_args { |
|---|
| 421 | my $poe = shift; |
|---|
| 422 | |
|---|
| 423 | my @ret; |
|---|
| 424 | for my $elem (@{$poe->args}) { |
|---|
| 425 | if ( ref $elem && ref $elem eq 'ARRAY') { |
|---|
| 426 | push @ret, [map { decode($poe->heap->{config}->{incode}, $_) } @$elem]; |
|---|
| 427 | } else { |
|---|
| 428 | push @ret, decode($poe->heap->{config}->{incode}, $elem); |
|---|
| 429 | } |
|---|
| 430 | } |
|---|
| 431 | return @ret; |
|---|
| 432 | } |
|---|
| 433 | |
|---|
| 434 | 1; |
|---|