OSDN Git Service

In non-mobile case, put menu links both above and below.
[keitairc/keitairc.git] / keitairc
1 #!/usr/bin/perl
2 # -*- mode: perl; coding: utf-8 -*-
3 # keitairc
4 # $Id: keitairc,v 1.73 2009-01-02 16:40:03 nyan_ Exp $
5 # $Source: /home/ishikawa/work/keitairc/tmp/keitairc/keitairc,v $
6 #
7 # Copyright (c) 2003-2008 Jun Morimoto <morimoto@mrmt.net>
8 # This program is covered by the GNU General Public License 2
9 #
10 # Depends: libpoe-component-irc-perl,
11 #   liburi-perl, libwww-perl, libappconfig-perl, libproc-daemon-perl,
12 #   libhtml-template-perl
13 #
14 # 00location_receiver plugin use XML::Simple, so if you want to use it
15 #    Depends: libxml-simple-perl
16
17 use Encode;
18 use POE;
19 use POE::Filter::HTTPD;
20 use POE::Component::IRC;
21 use POE::Component::Server::TCP;
22 use URI::Escape;
23 use HTML::Template;
24 use HTTP::Response;
25
26 use FindBin;
27 use lib ("$FindBin::Bin/lib", '/usr/share/keitairc/lib');
28 use Keitairc::Config;
29 use Keitairc::View;
30 use Keitairc::IrcBuffer;
31 use Keitairc::IrcCallback;
32 use Keitairc::ClientInfo;
33 use Keitairc::SessionManager;
34 use Keitairc::Plugins;
35 use Keitairc::Log;
36 use strict;
37 use warnings;
38
39 our $cf = new Keitairc::Config({version => '2.0b11', argv => \@ARGV});
40 our $ib = new Keitairc::IrcBuffer({history => $cf->web_lines()});
41 our $sm = new Keitairc::SessionManager({default_ttl => $cf->session_ttl()});
42 our $pl = new Keitairc::Plugins({config => $cf});
43
44 # daemonize
45 if($cf->daemonize()){
46         use Proc::Daemon;
47
48         Proc::Daemon::Init;
49         if(length $cf->pid_dir()){
50                 if (open(PID, '> ' . $cf->pid_dir() . '/' . $cf->pid_file())) {
51                         print PID $$, "\n";
52                         close(PID);
53                 }
54         }
55 }
56
57 # create irc component
58 our $irc = POE::Component::IRC->spawn(
59         Alias => 'keitairc_irc',
60         Nick => $cf->irc_nick(),
61         Username => $cf->irc_username(),
62         Ircname => $cf->irc_desc(),
63         Server => $cf->irc_server(),
64         Port => $cf->irc_port(),
65         Password => $cf->irc_password());
66
67 # create POE session
68 POE::Session->create(
69         heap => {
70                 seen_traffic => 0,
71                 disconnect_msg => 1,
72                 Config => $cf,
73                 Irc => $irc,
74                 IrcBuffer => $ib,
75         },
76         inline_states => {
77                 _start => \&Keitairc::IrcCallback::irc_start,
78                 autoping => \&Keitairc::IrcCallback::irc_autoping,
79                 connect => \&Keitairc::IrcCallback::irc_connect,
80                 irc_registered => \&Keitairc::IrcCallback::irc_registered,
81                 irc_001 => \&Keitairc::IrcCallback::irc_001,
82                 irc_join => \&Keitairc::IrcCallback::irc_join,
83                 irc_part => \&Keitairc::IrcCallback::irc_part,
84                 irc_quit => \&Keitairc::IrcCallback::irc_quit,
85                 irc_public => \&Keitairc::IrcCallback::irc_public,
86                 irc_notice => \&Keitairc::IrcCallback::irc_notice,
87                 irc_mode => \&Keitairc::IrcCallback::irc_mode,
88                 irc_nick => \&Keitairc::IrcCallback::irc_nick,
89                 irc_msg => \&Keitairc::IrcCallback::irc_msg,
90                 irc_topic => \&Keitairc::IrcCallback::irc_topic,
91                 irc_332 => \&Keitairc::IrcCallback::irc_topicraw,
92                 irc_352 => \&Keitairc::IrcCallback::irc_whoreply,
93                 irc_ctcp_action => \&Keitairc::IrcCallback::irc_ctcp_action,
94                 irc_disconnected => \&Keitairc::IrcCallback::irc_reconnect,
95                 irc_error => \&Keitairc::IrcCallback::irc_reconnect,
96                 irc_socketerr => \&Keitairc::IrcCallback::irc_reconnect,
97         });
98
99 # create web server component
100 POE::Component::Server::TCP->new(
101         Alias => 'keitairc',
102         Port => $cf->web_listen_port(),
103         ClientFilter => 'POE::Filter::HTTPD',
104         ClientInput => \&http_request);
105
106 # fire up main loop
107 $poe_kernel->run();
108 exit 0;
109
110 ################################################################
111 sub http_request{
112         my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0];
113
114         # Filter::HTTPD sometimes generates HTTP::Response objects.
115         # They indicate (and contain the response for) errors that occur
116         # while parsing the client's HTTP request.  It's easiest to send
117         # the responses as they are and finish up.
118         if($request->isa('HTTP::Response')){
119                 $heap->{client}->put($request);
120         }elsif(my $response = dispatch($request)){
121                 $heap->{client}->put($response);
122         }
123
124         $kernel->yield('shutdown');
125 }
126
127 ################################################################
128 sub dispatch{
129         my $request = shift;
130         my $uri = $request->uri();
131         my $ci = new Keitairc::ClientInfo($request);
132
133         Keitairc::Log::log_debug("dispatch: $uri");
134
135         {
136                 # chop off $cf->web_root()
137                 my $root = $cf->web_root();
138                 $uri =~ s|$root|/|;
139         }
140
141         if($uri eq '/'){
142                 return action_root($request);
143         }
144
145         if($uri eq '/login'){
146                 return action_login($request);
147         }
148
149         if($uri eq '/login_icc'){
150                 return action_login_icc($request);
151         }
152
153         if($uri eq '/login_imodeid?guid=ON'){
154                 return action_login_imodeid($request);
155         }
156
157         for my $name ($pl->list_action_plugins()){
158                 if($uri =~ m|^/(S[a-zA-Z]{10})/$name/(.*)| ||
159                    $uri =~ m|^/(S[a-zA-Z]{10})/$name$|){
160                         if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){
161                                 return add_cookie($pl->{plugins}->{$name}->{action_imprementation}($request, $name, $1, $2), $1);
162                         }
163                         return action_redirect_root($request);
164                 }
165         }
166
167         return action_public($request, $uri) || action_404($request);
168 }
169
170 ################################################################
171 # adds session id cookie to http response object
172 sub add_cookie{
173         my $response = shift;
174         my $session_id = shift;
175
176         my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime(time + $cf->cookie_ttl());
177         my $expiration =
178                 sprintf('%.3s, %.2d-%.3s-%.4s %.2d:%.2d:%.2d',
179                         qw(Sun Mon Tue Wed Thu Fri Sat)[$wday],
180                         $mday,
181                         qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)[$mon],
182                         $year + 1900,
183                         $hour,
184                         $min,
185                         $sec);
186         my $content = sprintf("sid=%s; expires=%s; path=%s; \n", $session_id, $expiration, $cf->web_root());
187         $response->push_header('Set-Cookie', $content);
188         $response;
189 }
190
191 ################################################################
192 # 通常ログインのPOST先
193 # パスワードをチェックして
194 # 間違っていたら / へリンクして終わり
195 # 合っていたらセッションを発行し /{SESSION}/index へ
196 sub action_login{
197         my $request = shift;
198         my $ci = new Keitairc::ClientInfo($request);
199         my $content = $request->decoded_content();
200         my ($password) = ($content =~ /^password=(.*)/);
201
202         Keitairc::Log::log_debug("password [$password]");
203         Keitairc::Log::log_debug("web_password [" . $cf->web_password() . "]");
204
205         if($cf->web_password() eq $password){
206                 my $s = $sm->add($ci->user_agent(), $ci->serial_key());
207                 my $view = new Keitairc::View($cf, $ci, $s->{id});
208                 return $view->redirect("/$s->{id}/index");
209         }
210
211         # password mismatch
212         my $view = new Keitairc::View($cf, $ci);
213         return $view->redirect('/');
214 }
215
216 ################################################################
217 sub action_404{
218         my $request = shift;
219         my $ci = new Keitairc::ClientInfo($request);
220         my $view = new Keitairc::View($cf, $ci);
221         return $view->render('404.html', { action => $request->uri() });
222 }
223
224 ################################################################
225 sub action_public{
226         my $request = shift;
227         my $uri = shift;        # such as '/favicon.ico'
228         my $ci = new Keitairc::ClientInfo($request);
229         my $view = new Keitairc::View($cf, $ci);
230         return $view->public($uri);
231 }
232
233 ################################################################
234 # かんたんログインのPOST先
235 # DoCoMoだったらiccが来ているはずなので, icc + user_agent でチェック。
236 # 合っていたらセッション復帰して /{SESSION}/index へ
237 sub action_login_icc{
238         my $request = shift;
239         my $ci = new Keitairc::ClientInfo($request);
240         if($ci->is_docomo()){
241                 my $docomo_foma_icc = $ci->docomo_foma_icc();
242                 if(length $docomo_foma_icc){
243                         if(my $s = $sm->verify({serial_key => $docomo_foma_icc,
244                                                 user_agent => $ci->user_agent()})){
245                                 Keitairc::Log::log_debug("redirect to /$s->{id}/index from docomo_foma_icc");
246                                 my $view = new Keitairc::View($cf, $ci, $s->{id});
247                                 return $view->redirect("/$s->{id}/index");
248                         }
249
250                         if($docomo_foma_icc eq $cf->docomo_foma_icc()){
251                                 my $s = $sm->add($ci->user_agent(), $docomo_foma_icc);
252                                 Keitairc::Log::log_debug("redirect to /$s->{id}/index from docomo_foma_icc");
253                                 my $view = new Keitairc::View($cf, $ci, $s->{id});
254                                 return $view->redirect("/$s->{id}/index");
255                         }
256
257                         my $view = new Keitairc::View($cf, $ci);
258                         return $view->render('login_icc.html', { icc => $docomo_foma_icc });
259                 }
260         }
261
262         my $view = new Keitairc::View($cf, $ci);
263         return $view->render('root.html', {
264                 docomo_foma_icc => $cf->docomo_foma_icc(),
265                 docomo_imodeid => $cf->docomo_imodeid(),
266                         });
267 }
268
269 ################################################################
270 # かんたんログインのPOST先
271 # DoCoMoだったらiモードIDが来ているはずなので, iモードID + user_agent でチェック。
272 # 合っていたらセッション復帰して /{SESSION}/index へ
273 sub action_login_imodeid{
274         my $request = shift;
275         my $ci = new Keitairc::ClientInfo($request);
276         if($ci->is_docomo()){
277                 my $docomo_imodeid = $ci->docomo_imodeid();
278                 if(length $docomo_imodeid){
279                         if(my $s = $sm->verify({serial_key => $docomo_imodeid,
280                                                 user_agent => $ci->user_agent()})){
281                                 Keitairc::Log::log_debug("redirect to /$s->{id}/index from docomo_imodeid");
282                                 my $view = new Keitairc::View($cf, $ci, $s->{id});
283                                 return $view->redirect("/$s->{id}/index");
284                         }
285
286                         if($docomo_imodeid eq $cf->docomo_imodeid()){
287                                 my $s = $sm->add($ci->user_agent(), $docomo_imodeid);
288                                 Keitairc::Log::log_debug("redirect to /$s->{id}/index from docomo_imodeid");
289                                 my $view = new Keitairc::View($cf, $ci, $s->{id});
290                                 return $view->redirect("/$s->{id}/index");
291                         }
292
293                         my $view = new Keitairc::View($cf, $ci);
294                         return $view->render('login_imodeid.html', { imodeid => $docomo_imodeid });
295                 }
296         }
297
298         my $view = new Keitairc::View($cf, $ci);
299         return $view->render('root.html', {
300                 docomo_foma_icc => $cf->docomo_foma_icc(),
301                 docomo_imodeid => $cf->docomo_imodeid(),
302                         });
303 }
304
305 ################################################################
306 sub action_root{
307         my $request = shift;
308         my $ci = new Keitairc::ClientInfo($request);
309
310         if($ci->cookie_available()){
311                 my $session_id = $ci->{cookie}->{sid};
312                 if(defined($session_id) && length($session_id)){
313                         if($sm->verify({session_id => $session_id,
314                                         user_agent => $ci->user_agent()})){
315                                 Keitairc::Log::log_debug("redirect to /$session_id/index from cookie");
316                                 my $view = new Keitairc::View($cf, $ci, $session_id);
317                                 return $view->redirect("/$session_id/index");
318                         }
319                 }
320         }
321
322         if($ci->is_ezweb()){
323                 my $subscriber_id = $ci->au_subscriber_id();
324                 if(length $subscriber_id){
325                         if(my $s = $sm->verify({serial_key => $subscriber_id,
326                                                 user_agent => $ci->user_agent()})){
327                                 Keitairc::Log::log_debug("redirect to /$s->{id}/index from subscriber_id");
328                                 my $view = new Keitairc::View($cf, $ci, $s->{id});
329                                 return $view->redirect("/$s->{id}/index");
330                         }
331
332                         if($subscriber_id eq $cf->au_subscriber_id()){
333                                 my $s = $sm->add($ci->user_agent(), $subscriber_id);
334                                 Keitairc::Log::log_debug("redirect to /$s->{id}/index from au_subscriber_id");
335                                 my $view = new Keitairc::View($cf, $ci, $s->{id});
336                                 return $view->redirect("/$s->{id}/index");
337                         }
338                 }
339         }
340
341         if($ci->is_softbank()){
342                 my $serial_key = $ci->softbank_serial();
343                 if(length $serial_key){
344                         if(my $s = $sm->verify({serial_key => $serial_key,
345                                                 user_agent => $ci->user_agent()})){
346                                 Keitairc::Log::log_debug("redirect to /$s->{id}/index from softbank serial_key");
347                                 my $view = new Keitairc::View($cf, $ci, $s->{id});
348                                 return $view->redirect("/$s->{id}/index");
349                         }
350                         if($serial_key eq $cf->softbank_serial_key()){
351                                 my $s = $sm->add($ci->user_agent(), $serial_key);
352                                 Keitairc::Log::log_debug("redirect to /$s->{id}/index from softbank_serial_key");
353                                 my $view = new Keitairc::View($cf, $ci, $s->{id});
354                                 return $view->redirect("/$s->{id}/index");
355                         }
356                 }
357         }
358
359         if($ci->is_emobile()){
360                 my $userid = $ci->emobile_userid();
361                 if(length $userid){
362                         if(my $s = $sm->verify({serial_key => $userid,
363                                                 user_agent => $ci->user_agent()})){
364                                 Keitairc::Log::log_debug("redirect to /$s->{id}/index from userid");
365                                 my $view = new Keitairc::View($cf, $ci, $s->{id});
366                                 return $view->redirect("/$s->{id}/index");
367                         }
368
369                         if($userid eq $cf->emobile_userid()){
370                                 my $s = $sm->add($ci->user_agent(), $userid);
371                                 Keitairc::Log::log_debug("redirect to /$s->{id}/index from emobile_userid");
372                                 my $view = new Keitairc::View($cf, $ci, $s->{id});
373                                 return $view->redirect("/$s->{id}/index");
374                         }
375                 }
376         }
377
378         my $view = new Keitairc::View($cf, $ci);
379         return $view->render('root.html', {
380                 docomo_foma_icc => $cf->docomo_foma_icc(),
381                 docomo_imodeid => $cf->docomo_imodeid(),
382                         });
383 }
384
385 ################################################################
386 sub action_redirect_root{
387         my $request = shift;
388         my $ci = new Keitairc::ClientInfo($request);
389         my $view = new Keitairc::View($cf, $ci);
390         return $view->redirect('/');
391 }
392
393 ################################################################
394 sub send_message{
395         my $request = shift;
396         my $channel = shift;
397         my $ci = new Keitairc::ClientInfo($request);
398         my $timestamp;
399
400         my $message = $request->content();
401         if(length($message)){
402                 ($message, $timestamp) = split(/&/, $message);
403                 $timestamp =~ s/^stamp=//g; 
404         }
405         $message =~ s/^m=//;
406         $message =~ s/\+/ /g;
407         if(length($message)){
408                 $message = uri_unescape($message);
409                 if($ci->is_ipod()){
410                         $message = fix_iui_escape($message);
411                 }
412         }
413
414         if(length($message)){
415                 my $jis = $message;
416                 Encode::from_to($jis, $cf->web_charset(), $cf->irc_charset());
417                 my $euc = Encode::decode($cf->web_charset(), $message);
418                 if($jis =~ s|^/||) {
419                         my ($params, $trailing) = split(/ :/, $jis, 2);
420                         my @postcmd = split(/ /, $params);
421                         push @postcmd, $trailing if defined $trailing;
422                         # This parser may be incomplete.
423                         if($postcmd[0] =~ /join/i) {
424                                 if($postcmd[1] =~ /^\w/) {
425                                         $ib->join($postcmd[1]);
426                                         return;
427                                 }
428                         } elsif($postcmd[0] =~ /part/i) {
429                                 if($postcmd[1] =~ /^\w/) {
430                                         $ib->part($ib->name2cid($postcmd[1]));
431                                         return;
432                                 }
433                         }
434                         $irc->yield(@postcmd);
435                 } elsif(length($channel)){
436                         if($ib->update_timestamp($timestamp)){
437                                 $irc->yield(privmsg => $channel => $jis);
438                                 my $cid = $ib->name2cid($channel);
439                                 $ib->add_message($cid, $euc, $cf->irc_nick());
440                         }
441                 }
442         }
443 }
444
445 ################################################################
446 # posted string from iPhone/iPod touch (with iui framework)
447 # contains escaped utf-8 in the form %uXXXX
448 # and may contains escaped Shift-JIS (web_charset) in the form \xXX
449 # when operated from Safari/Mac OS X
450 sub fix_iui_escape{
451         # charset: $cf->irc_charset()
452         my $in = shift;
453         $in =~ s/\\x([0-9A-F]{2})/pack('C',hex($1))/egi;
454         my $pi = Encode::decode($cf->web_charset(), $in);
455         $pi =~ s/%u([0-9A-F]{4})/pack('U',hex($1))/egi;
456         return Encode::encode($cf->web_charset(), $pi);
457 }
458
459 __END__