3 # $Id: keitairc,v 1.34 2008-01-08 05:52:14 morimoto Exp $
4 # $Source: /home/ishikawa/work/keitairc/tmp/keitairc/keitairc,v $
6 # Copyright (c) 2003-2008 Jun Morimoto <morimoto@mrmt.net>
7 # This program is covered by the GNU General Public License 2
9 # Depends: libpoe-component-irc-perl,
10 # liburi-perl, libwww-perl, libappconfig-perl, libproc-daemon-perl,
11 # libhtml-template-perl
13 use lib qw(lib /usr/share/keitairc/lib);
17 use POE::Filter::HTTPD;
18 use POE::Component::IRC;
19 use POE::Component::Server::TCP;
26 use Keitairc::ClientInfo;
27 use Keitairc::IrcBuffer;
28 use Keitairc::IrcCallback;
29 use Keitairc::SessionManager;
31 our $cf = new Keitairc::Config('2.0b1', @ARGV);
32 our $ib = new Keitairc::IrcBuffer({history => $cf->web_lines()});
33 our $sm = new Keitairc::SessionManager({default_ttl => $cf->session_ttl()});
38 if(length $cf->pid_dir()){
39 if (open(PID, '> ' . $cf->pid_dir() . '/keitairc.pid')) {
46 # create irc component
47 our $irc = POE::Component::IRC->spawn(
48 Alias => 'keitairc_irc',
49 Nick => $cf->irc_nick(),
50 Username => $cf->irc_username(),
51 Ircname => $cf->irc_desc(),
52 Server => $cf->irc_server(),
53 Port => $cf->irc_port(),
54 Password => $cf->irc_password());
66 _start => \&Keitairc::IrcCallback::irc_start,
67 autoping => \&Keitairc::IrcCallback::irc_autoping,
68 connect => \&Keitairc::IrcCallback::irc_connect,
69 irc_001 => \&Keitairc::IrcCallback::irc_001,
70 irc_join => \&Keitairc::IrcCallback::irc_join,
71 irc_part => \&Keitairc::IrcCallback::irc_part,
72 irc_public => \&Keitairc::IrcCallback::irc_public,
73 irc_notice => \&Keitairc::IrcCallback::irc_notice,
74 irc_topic => \&Keitairc::IrcCallback::irc_topic,
75 irc_332 => \&Keitairc::IrcCallback::irc_topicraw,
76 irc_ctcp_action => \&Keitairc::IrcCallback::irc_ctcp_action,
77 irc_disconnected => \&Keitairc::IrcCallback::irc_reconnect,
78 irc_error => \&Keitairc::IrcCallback::irc_reconnect,
79 irc_socketerr => \&Keitairc::IrcCallback::irc_reconnect,
82 # create web server component
83 POE::Component::Server::TCP->new(
85 Port => $cf->web_port(),
86 ClientFilter => 'POE::Filter::HTTPD',
87 ClientInput => \&http_request);
93 ################################################################
95 my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0];
97 # Filter::HTTPD sometimes generates HTTP::Response objects.
98 # They indicate (and contain the response for) errors that occur
99 # while parsing the client's HTTP request. It's easiest to send
100 # the responses as they are and finish up.
101 if($request->isa('HTTP::Response')){
102 $heap->{client}->put($request);
103 }elsif(my $response = dispatch($request)){
104 $heap->{client}->put($response);
107 $kernel->yield('shutdown');
110 ################################################################
113 my $uri = $request->uri();
114 my $ci = new Keitairc::ClientInfo($request);
116 ::log_debug("dispatch: $uri");
119 return action_root($request);
122 if($uri eq '/login'){
123 return action_login($request);
126 if($uri eq '/quicklogin'){
127 return action_quicklogin($request);
130 if($uri =~ m|^/(S[a-zA-Z]{10})/index|){
131 if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){
132 return add_cookie(action_index($request), $1);
134 return action_redirect_root($request);
137 if($uri =~ m|^/(S[a-zA-Z]{10})/topic|){
138 if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){
139 return add_cookie(action_topic($request), $1);
141 return action_redirect_root($request);
144 if($uri =~ m|^/(S[a-zA-Z]{10})/recent|){
145 if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){
146 return add_cookie(action_recent($request), $1);
148 return action_redirect_root($request);
151 if($uri =~ m|^/(S[a-zA-Z]{10})/all/(.*)|){
152 if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){
153 return add_cookie(action_all($request, $2), $1);
155 return action_redirect_root($request);
158 if($uri =~ m|^/(S[a-zA-Z]{10})/unread/(.*)|){
159 if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){
160 return add_cookie(action_unread($request, $2), $1);
162 return action_redirect_root($request);
165 if($uri =~ m|^/(S[a-zA-Z]{10})/phone/(\d+)|){
166 if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){
167 return add_cookie(action_phone($request, $2), $1);
169 return action_redirect_root($request);
172 if($uri =~ m|^/(S[a-zA-Z]{10})/mail/(.*)|){
173 if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){
174 return add_cookie(action_mail($request, $2), $1);
176 return action_redirect_root($request);
179 if($uri =~ m|^/(S[a-zA-Z]{10})/url/(.*)|){
180 if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){
181 return add_cookie(action_url($request, $2, $1), $1);
183 return action_redirect_root($request);
186 ::log("dispatch: don't know how to dispatch uri[$uri]");
187 return action_404($request);
190 ################################################################
191 # adds session id cookie to http response object
193 my $response = shift;
194 my $session_id = shift;
196 my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime(time + $cf->cookie_ttl());
198 sprintf('%.3s, %.2d-%.3s-%.4s %.2d:%.2d:%.2d',
199 qw(Sun Mon Tue Wed Thu Fri Sat)[$wday],
201 qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)[$mon],
206 my $content = sprintf("sid=%s; expires=%s; \n", $session_id, $expiration);
207 $response->push_header('Set-Cookie', $content);
211 ################################################################
212 # /{SESSION}/all/{CHANNEL}
213 #
\83`
\83\83\83l
\83\8b\82Ì
\83\81\83b
\83Z
\81[
\83W
\89{
\97\97
216 # Apart from the the requirement that the first character being either
217 # '&', '#', '+' or '!' (hereafter called "channel prefix"). The only
218 # restriction on a channel name is that it SHALL NOT contain any
219 # spaces (' '), a control G (^G or ASCII 7), a comma (',' which is
220 # used as a list item separator by the protocol). Also, a colon (':')
221 # is used as a delimiter for the channel mask. The exact syntax of a
222 # channel name is defined in "IRC Server Protocol" [IRC-SERVER]. so
223 # we use white space as separator character of channel name and
228 $channel = uri_unescape($channel);
230 send_message($request, $channel);
233 my $no_message_here_yet;
234 if(defined($ib->name($channel))){
235 if(length($ib->buffer($channel))){
236 if($cf->show_newmsgonly() && $ib->message_added()){
237 $buf = render_line($ib->unread($channel)) ||
238 '(
\96¢
\93Ç
\94
\8c¾
\82Í
\82 \82è
\82Ü
\82¹
\82ñ)';
239 $buf .= sprintf('<br /><a accesskey="5" href="../all/%s">[5]
\91S
\94
\8c¾
\82Ö</a>',
240 uri_escape($channel));
242 $buf = render_line($ib->buffer($channel));
245 $no_message_here_yet = 1;
248 $buf = '
\8ew
\92è
\82ÌÁ¬ÈÙ
\82Í
\91¶
\8dÝ
\82µ
\82Ü
\82¹
\82ñ';
251 $ib->message_added(0); # clear check flags
252 $ib->clear_unread($channel);
254 my $ci = new Keitairc::ClientInfo($request);
255 my $view = new Keitairc::View($cf, $ci);
256 return $view->render('all.html', {
258 channel_compact => $ib->compact_channel_name($ib->name($channel)),
259 channel => uri_escape($channel),
260 no_message_here_yet => $no_message_here_yet,
261 ipod => $ci->is_ipod(),
265 ################################################################
266 # /{SESSION}/unread/{CHANNEL}
267 #
\83`
\83\83\83l
\83\8b\82Ì
\83\81\83b
\83Z
\81[
\83W
\89{
\97\97
271 $channel = uri_unescape($channel);
273 send_message($request, $channel);
276 my $no_message_here_yet;
277 if(defined($ib->name($channel))){
278 if(length($ib->buffer($channel))){
279 $buf = render_line($ib->unread($channel)) ||
280 '(
\96¢
\93Ç
\94
\8c¾
\82Í
\82 \82è
\82Ü
\82¹
\82ñ)';
281 $buf .= sprintf('<hr /><a accesskey="5" href="../all/%s">[5]
\91S
\94
\8c¾
\82Ö</a><br />',
282 uri_escape($channel));
284 $no_message_here_yet = 1;
287 $buf = '
\8ew
\92è
\82ÌÁ¬ÈÙ
\82Í
\91¶
\8dÝ
\82µ
\82Ü
\82¹
\82ñ';
290 $ib->message_added(0); # clear check flags
291 $ib->clear_unread($channel);
293 my $ci = new Keitairc::ClientInfo($request);
294 my $view = new Keitairc::View($cf, $ci);
295 return $view->render('unread.html', {
297 channel_compact => $ib->compact_channel_name($ib->name($channel)),
298 channel => uri_escape($channel),
299 no_message_here_yet => $no_message_here_yet,
300 ipod => $ci->is_ipod(),
304 ################################################################
306 #
\83g
\83s
\83b
\83N
\88ê
\97\97
311 for my $channel ($ib->channels()){
313 $buf .= sprintf(' <a href="all/%s">%s</a><br />',
314 uri_escape($channel),
315 $ib->compact_channel_name($ib->name($channel)));
317 if($topic = $ib->topic($channel)){
318 Encode::from_to($topic, 'jis', 'shiftjis');
320 $topic = '(ÄË߯¸
\96¢
\90Ý
\92è)';
326 my $ci = new Keitairc::ClientInfo($request);
327 my $view = new Keitairc::View($cf, $ci);
328 return $view->render('topic.html', { buf => $buf });
331 ################################################################
332 # /{SESSION}/phone/{PARAM}
338 my $ci = new Keitairc::ClientInfo($request);
339 my $view = new Keitairc::View($cf, $ci);
340 return $view->render('phone.html', {
342 docomo => $ci->is_docomo(),
346 ################################################################
347 # /{SESSION}/mail/{PARAM}
353 my $ci = new Keitairc::ClientInfo($request);
354 my $view = new Keitairc::View($cf, $ci);
355 return $view->render('mail.html', {
360 ################################################################
361 # /{SESSION}/url/{PARAM}
366 my $session_id = shift;
368 my $ci = new Keitairc::ClientInfo($request);
369 my $view = new Keitairc::View($cf, $ci);
370 return $view->render('url.html', {
372 escaped_url => uri_escape($url),
373 ezweb => $ci->is_ezweb(),
378 ################################################################
380 # recent messages on every channel
385 for my $channel ($ib->channels()){
386 if($ib->unread_lines($channel)){
387 my $name = $ib->name($channel);
388 Encode::from_to($name, 'jis', 'shiftjis');
390 $buf .= sprintf(' <a href="all/%s">
\91S
\94
\8c¾
\82Ö</a><br />', uri_escape($channel));
391 $buf .= render_line($ib->unread($channel), '.');
393 $ib->clear_unread($channel);
397 my $ci = new Keitairc::ClientInfo($request);
398 my $view = new Keitairc::View($cf, $ci);
399 return $view->render('recent.html', { buf => $buf });
402 ################################################################
404 #
\83`
\83\83\83l
\83\8b\88ê
\97\97
407 my $unread_channels = 0;
411 for my $channel ($ib->channels()){
413 $buf .= sprintf('<a accesskey="%1d" href="all/%s">[%1d] %s</a>',
415 uri_escape($channel),
417 $ib->compact_channel_name($ib->name($channel)));
419 $buf .= sprintf('<a href="all/%s"> %s</a>',
420 uri_escape($channel),
421 $ib->compact_channel_name($ib->name($channel)));
426 if($ib->unread_lines($channel)){
427 $buf .= sprintf(' <a href="unread/%s">%s</a>',
428 uri_escape($channel),
429 $ib->unread_lines($channel));
435 my $ci = new Keitairc::ClientInfo($request);
436 my $view = new Keitairc::View($cf, $ci);
437 return $view->render('index.html',
440 unread => $unread_channels,
444 ################################################################
445 #
\92Ê
\8fí
\83\8d\83O
\83C
\83\93\82ÌPOST
\90æ
446 #
\83p
\83X
\83\8f\81[
\83h
\82ð
\83`
\83F
\83b
\83N
\82µ
\82Ä
447 #
\8aÔ
\88á
\82Á
\82Ä
\82¢
\82½
\82ç /
\82Ö
\83\8a\83\93\83N
\82µ
\82Ä
\8fI
\82í
\82è
448 #
\8d\87\82Á
\82Ä
\82¢
\82½
\82ç
\83Z
\83b
\83V
\83\87\83\93\82ð
\94
\8ds
\82µ /{SESSION}/index
\82Ö
451 my $ci = new Keitairc::ClientInfo($request);
452 my $content = $request->decoded_content();
453 my ($password) = ($content =~ /^password=(.*)/);
455 ::log_debug("password [$password]");
456 ::log_debug("web_password [" . $cf->web_password() . "]");
458 if($cf->web_password() eq $password){
459 my $s = $sm->add($ci->{header}->{user_agent}, $ci->serial_key());
460 my $view = new Keitairc::View($cf, $ci, $s->{id});
461 return $view->redirect("/$s->{id}/index");
465 my $view = new Keitairc::View($cf, $ci);
466 return $view->redirect("/");
469 ################################################################
472 my $ci = new Keitairc::ClientInfo($request);
473 my $view = new Keitairc::View($cf, $ci);
474 return $view->render('404.html', { action => $request->uri() });
477 ################################################################
478 #
\82©
\82ñ
\82½
\82ñ
\83\8d\83O
\83C
\83\93\82ÌPOST
\90æ
479 # DoCoMo
\82¾
\82Á
\82½
\82çicc
\82ª
\97\88\82Ä
\82¢
\82é
\82Í
\82¸
\82È
\82Ì
\82Å, icc + user_agent
\82Å
\83`
\83F
\83b
\83N
\81B
480 #
\8d\87\82Á
\82Ä
\82¢
\82½
\82ç
\83Z
\83b
\83V
\83\87\83\93\95\9c\8bA
\82µ
\82Ä /{SESSION}/index
\82Ö
481 sub action_quicklogin{
483 my $ci = new Keitairc::ClientInfo($request);
484 if($ci->is_docomo()){
485 my $docomo_foma_icc = $ci->docomo_foma_icc();
486 if(length $docomo_foma_icc){
487 if(my $s = $sm->verify({serial_key => $docomo_foma_icc,
488 user_agent => $ci->user_agent()})){
489 ::log_debug("redirect to /$s->{id}/index from docomo_foma_icc");
490 my $view = new Keitairc::View($cf, $ci, $s->{id});
491 return $view->redirect("/$s->{id}/index");
494 if($docomo_foma_icc eq $cf->docomo_foma_icc()){
495 my $s = $sm->add($ci->user_agent(), $docomo_foma_icc);
496 ::log_debug("redirect to /$s->{id}/index from docomo_foma_icc");
497 my $view = new Keitairc::View($cf, $ci, $s->{id});
498 return $view->redirect("/$s->{id}/index");
503 my $view = new Keitairc::View($cf, $ci);
504 return $view->render('root.html', { docomo => $ci->is_docomo() });
507 ################################################################
510 my $ci = new Keitairc::ClientInfo($request);
512 if($ci->cookie_available()){
513 my $session_id = $ci->{cookie}->{sid};
514 if(length $session_id){
515 if($sm->verify({session_id => $session_id,
516 user_agent => $ci->user_agent()})){
517 ::log_debug("redirect to /$session_id/index from cookie");
518 my $view = new Keitairc::View($cf, $ci, $session_id);
519 return $view->redirect("/$session_id/index");
525 my $subscriber_id = $ci->{header}->{x_up_subno};
526 if(length $subscriber_id){
527 if(my $s = $sm->verify({serial_key => $subscriber_id,
528 user_agent => $ci->user_agent()})){
529 ::log_debug("redirect to /$s->{id}/index from subscriber_id");
530 my $view = new Keitairc::View($cf, $ci, $s->{id});
531 return $view->redirect("/$s->{id}/index");
534 if($subscriber_id eq $cf->au_subscriber_id()){
535 my $s = $sm->add($ci->user_agent(), $subscriber_id);
536 ::log_debug("redirect to /$s->{id}/index from au_subscriber_id");
537 my $view = new Keitairc::View($cf, $ci, $s->{id});
538 return $view->redirect("/$s->{id}/index");
543 if($ci->is_softbank()){
544 my $serial_key = $ci->softbank_serial();
545 if(length $serial_key){
546 if(my $s = $sm->verify({serial_key => $serial_key,
547 user_agent => $ci->user_agent()})){
548 ::log_debug("redirect to /$s->{id}/index from softbank serial_key");
549 my $view = new Keitairc::View($cf, $ci, $s->{id});
550 return $view->redirect("/$s->{id}/index");
555 my $view = new Keitairc::View($cf, $ci);
556 return $view->render('root.html', { docomo => $ci->is_docomo() });
559 ################################################################
560 sub action_redirect_root{
562 my $ci = new Keitairc::ClientInfo($request);
563 my $view = new Keitairc::View($cf, $ci);
564 return $view->redirect("/");
567 ################################################################
572 my $message = $request->content();
574 $message =~ s/\+/ /g;
575 $message = uri_unescape($message);
577 if(length($message)){
580 Encode::from_to($jis, 'shiftjis', 'jis');
581 Encode::from_to($euc, 'shiftjis', 'euc-jp');
582 $irc->yield(privmsg => $channel => $jis);
583 $ib->add_message($channel, $euc, $cf->irc_nick());
584 $ib->message_added(1);
588 ################################################################
589 #
\93ü
\97Í
\82Í euc-jp
596 unless(defined $depth){
600 for ((reverse(split("\n", $in)))[0 .. $cf->web_lines()]){
604 $_ = $ib->simple_escape($_);
606 unless(s|\b(https?://[/!-;=-\177]+)|link_url($1, $depth)|eg){
607 unless(s|\b(www\.[/!-\177]+)|link_url($1, $depth)|eg){
609 unless(s|\b(0\d{1,3})([-(]?)(\d{2,4})([-)]?)(\d{4})\b|<a href="$depth/phone/$1$3$5">$1$2$3$4$5</a>|g){
610 s|\b(\w[\w.+=-]*\@[\w.-]+[\w]\.[\w]{2,4})\b|<a href="$depth/mail/$1">$1</a>|g;
620 Encode::from_to($buf, 'euc-jp', 'shiftjis');
624 ################################################################
628 sprintf('<a href="%s/url/%s">%s</a>', $depth, $url, $url);
631 ################################################################
634 warn "keitairc: $m\n";
640 die "keitairc: $m\n";
646 warn "keitairc: $m\n";