3 # $Id: keitairc,v 1.6 2004-03-21 11:02:27 morimoto Exp $
5 # Copyright (c) 2003 Jun Morimoto <morimoto@xantia.citroen.org>
6 # This program is covered by the GNU General Public License 2
8 # Depends: libjcode-pm-perl, libpoe-component-irc-perl,
9 # liburi-perl, libwww-perl, libappconfig-perl
11 my $rcsid = q$Id: keitairc,v 1.6 2004-03-21 11:02:27 morimoto Exp $;
12 my ($version) = $rcsid =~ m#,v ([0-9.]+)#;
17 use POE::Component::Server::TCP;
18 use POE::Component::IRC;
19 use POE::Filter::HTTPD;
22 use AppConfig qw(:argcount);
24 my $config = AppConfig->new(
28 ARGCOUNT => ARGCOUNT_ONE,
31 qw(irc_nick irc_username irc_desc
32 irc_server irc_port irc_password
33 web_port web_title web_lines web_root
34 web_username web_password)
37 $config->file('/etc/keitairc');
38 $config->file($ENV{'HOME'} . '/.keitairc');
42 if(defined $config->web_root){
43 $docroot = $config->web_root;
46 # join
\e$B$7$F$$$k%A%c%M%k$NL>>N$r5-O?$9$k%O%C%7%e
\e(B
49 #
\e$B%A%c%M%k$N2qOCFbMF$r5-O?$9$k%O%C%7%e
\e(B
52 #
\e$B3F%A%c%M%k$N:G=*%"%/%;%9;~9o!":G?7H/8@;~9o
\e(B
56 POE::Component::IRC->new('keitairc');
58 _start => \&on_irc_start,
59 irc_join => \&on_irc_join,
60 irc_part => \&on_irc_part,
61 irc_public => \&on_irc_public,
62 irc_notice => \&on_irc_notice,
65 # web server component
66 POE::Component::Server::TCP->new(
68 Port => $config->web_port,
69 ClientFilter => 'POE::Filter::HTTPD',
70 ClientInput => \&on_web_request
76 ################################################################
78 my $kernel = $_[KERNEL];
79 $kernel->post('keitairc' => 'register' => 'all');
80 $kernel->post('keitairc' => 'connect' => {
81 Nick => $config->irc_nick,
82 Username => $config->irc_username,
83 Ircname => $config->irc_desc,
84 Server => $config->irc_server,
85 Port => $config->irc_port,
86 Password => $config->irc_password
90 ################################################################
92 my ($kernel, $who, $channel) = @_[KERNEL, ARG0, ARG1];
93 $channel_name{$channel}++;
96 ################################################################
98 my ($kernel, $who, $channel) = @_[KERNEL, ARG0, ARG1];
100 # chop off after the gap (bug workaround of POE::Filter::IRC)
103 delete $channel_name{$channel};
106 ################################################################
108 my ($kernel, $who, $channel, $msg) = @_[KERNEL, ARG0 .. ARG2];
110 $channel = $channel->[0];
111 $msg = Jcode->new($msg, 'jis')->euc;
112 &add_message($channel, $who, $msg);
115 ################################################################
117 my ($kernel, $who, $channel, $msg) = @_[KERNEL, ARG0 .. ARG2];
119 $channel = $channel->[0];
120 $msg = Jcode->new($msg, 'jis')->euc;
121 &add_message($channel, $who, $msg);
124 ################################################################
125 # $msg
\e$B$O
\e(B EUC
\e$B$K$J$C$F$$$k$O$:
\e(B
126 # $channel
\e$B$O
\e(B jis
\e$B$G$-$F$k$>
\e(B
128 my($channel, $who, $msg) = @_;
130 my @tmp = split("\n", $channel_buffer{$channel});
131 push @tmp, sprintf('%s %s> %s', &now, $who, $msg);
133 if(@tmp > $config->web_lines){
134 $channel_buffer{$channel} = join("\n", splice(@tmp, -$config->web_lines));
136 $channel_buffer{$channel} = join("\n", @tmp);
139 $mtime{$channel} = time;
142 ################################################################
144 my ($sec,$min,$hour) = localtime(time);
145 sprintf('%02d:%02d', $hour, $min);
148 ################################################################
157 ################################################################
159 my $accesskey = shift;
162 sprintf('%d ', $accesskey);
168 ################################################################
173 for my $channel (sort {
174 $mtime{$b} <=> $mtime{$a};
175 }(keys(%channel_name))){
177 $buf .= &label($accesskey);
179 unless(defined($channel_buffer{$channel})){
180 $buf .= &compact_channel_name($channel);
183 $buf .= sprintf('<a accesskey="%1d" href="%s%s">%s</a>',
186 uri_escape($channel),
187 &compact_channel_name($channel));
189 $buf .= sprintf('<a href="%docroot%s">%s</a>',
191 uri_escape($channel),
192 &compact_channel_name($channel));
198 if($atime{$channel} < $mtime{$channel}){
205 $buf .= qq(<a href="$docroot" accesskey="0"></a>);
206 $buf .= qq( - keitairc $version);
211 ################################################################
212 #
\e$B%A%c%M%kL>>N$rC;$+$/$9$k
\e(B
213 sub compact_channel_name{
216 # #name:*.jp
\e$B$r
\e(B %name
\e$B$K
\e(B
221 #
\e$BKvHx$NC1FH$N
\e(B @
\e$B$O<h$k
\e(B (for multicast.plm)
227 ################################################################
232 my @src = (reverse(split("\n", shift)))[0 .. $config->web_lines];
240 unless(s,(http://[!-;=-\177]+),<a href="$1">$1</a>,g){
241 unless(s|(www\.[!-\177]+)|<A HREF="http://$1">$1</A>|g){
243 unless(s|(0\d{1,3}[-(]?\d{2,4}[-)]?\d{4})|<a href="tel:$1">$1</a>|g){
244 s|(\w[\w.+=-]+\@[\w.-]+[\w])|<a href="mailto:$1">$1</a>|g;
254 '<pre>' . join("\n", @buf) . '</pre>';
257 ################################################################
259 my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0];
261 # Filter::HTTPD sometimes generates HTTP::Response objects.
262 # They indicate (and contain the response for) errors that occur
263 # while parsing the client's HTTP request. It's easiest to send
264 # the responses as they are and finish up.
265 if($request->isa('HTTP::Response')){
266 $heap->{client}->put($request);
267 $kernel->yield('shutdown');
271 if(defined($config->web_username)){
272 unless($request->headers->authorization_basic eq
273 $config->web_username . ':' . $config->web_password){
275 my $response = HTTP::Response->new(401);
276 $response->push_header(WWW_Authenticate =>
277 qq(Basic Realm="keitairc"));
278 $heap->{client}->put($response);
279 $kernel->yield('shutdown');
284 my $uri = $request->uri;
285 my $content = '<html><head>';
287 # POST
\e$B$5$l$F$-$?$b$N$OH/8@
\e(B
288 if($request->method =~ /POST/i){
289 my $message = $request->content;
291 $message =~ s/\+/ /g;
292 $message = uri_unescape($message);
294 if(length($message)){
296 my $channel = uri_unescape($uri);
297 $poe_kernel->post('keitairc',
299 Jcode->new($channel)->jis,
300 Jcode->new($message)->jis);
301 &add_message($channel, $config->irc_nick,
302 Jcode->new($message)->euc);
307 $content .= '<title>' . $config->web_title . '</title>';
308 $content .= '<body>';
309 $content .= &index_page;
312 my $channel = uri_unescape($uri);
314 $content .= '<title>' . $config->web_title . ": $channel</title>";
315 $content .= '<body>';
317 $content .= '<a name="1"></a>';
318 $content .= '<a accesskey="7" href="#1"></a>';
320 $content .= sprintf('<form action="%s%s" method="post">',
322 $content .= '<input type="text" name="m" size="10">';
323 $content .= '<input type="submit" accesskey="1" value="OK">';
324 # $content .= '<input type="submit" accesskey="1" value="聆">';
325 $content .= '</form>';
327 if(defined($channel_name{$channel})){
328 if(defined($channel_buffer{$channel}) &&
329 length($channel_buffer{$channel})){
330 $content .= qq(<a accesskey="8" href="$docroot"></a>);
331 $content .= '<a accesskey="9" href="#2"></a>';
332 $content .= &render($channel_buffer{$channel});
333 $content .= '<a name="2"></a>';
335 $content .= 'no message here yet';
338 $content .= "no such channel";
341 $atime{$channel} = time;
344 $content .= '</body></html>';
346 my $response = HTTP::Response->new(200);
347 $response->push_header('Content-type', 'text/html; charset=Shift_JIS');
348 $response->content(Jcode->new($content)->sjis);
349 $heap->{client}->put($response);
350 $kernel->yield('shutdown');