| 1 | package Mobirc::HTTPD::Controller; |
|---|
| 2 | use strict; |
|---|
| 3 | use warnings; |
|---|
| 4 | use boolean ':all'; |
|---|
| 5 | |
|---|
| 6 | use Carp; |
|---|
| 7 | use CGI; |
|---|
| 8 | use Encode; |
|---|
| 9 | use Template; |
|---|
| 10 | use File::Spec; |
|---|
| 11 | use URI::Find; |
|---|
| 12 | use URI::Escape; |
|---|
| 13 | use HTTP::Response; |
|---|
| 14 | use HTML::Entities; |
|---|
| 15 | use Scalar::Util qw/blessed/; |
|---|
| 16 | use List::Util qw/first/; |
|---|
| 17 | use CGI::Cookie; |
|---|
| 18 | |
|---|
| 19 | use Mobirc; |
|---|
| 20 | use Mobirc::Util; |
|---|
| 21 | |
|---|
| 22 | sub call { |
|---|
| 23 | my ($class, $method, @args) = @_; |
|---|
| 24 | DEBUG "CALL METHOD $method with @args"; |
|---|
| 25 | $class->$method(@args); |
|---|
| 26 | } |
|---|
| 27 | |
|---|
| 28 | # this module contains MVC's C. |
|---|
| 29 | |
|---|
| 30 | sub dispatch_index { |
|---|
| 31 | my ($class, $c) = @_; |
|---|
| 32 | |
|---|
| 33 | return render( |
|---|
| 34 | $c, |
|---|
| 35 | 'index' => { |
|---|
| 36 | exists_recent_entries => ( |
|---|
| 37 | grep( $c->{irc_heap}->{unread_lines}->{$_}, keys %{ $c->{irc_heap}->{unread_lines} } ) |
|---|
| 38 | ? true |
|---|
| 39 | : false |
|---|
| 40 | ), |
|---|
| 41 | canon_channels => [ |
|---|
| 42 | reverse |
|---|
| 43 | sort { |
|---|
| 44 | $c->{irc_heap}->{channel_mtime}->{$a} <=> $c->{irc_heap}->{channel_mtime}->{$b} |
|---|
| 45 | } |
|---|
| 46 | keys %{ $c->{irc_heap}->{channel_name} } |
|---|
| 47 | ], |
|---|
| 48 | } |
|---|
| 49 | ); |
|---|
| 50 | } |
|---|
| 51 | |
|---|
| 52 | # recent messages on every channel |
|---|
| 53 | sub dispatch_recent { |
|---|
| 54 | my ($class, $c) = @_; |
|---|
| 55 | |
|---|
| 56 | my $out = render( |
|---|
| 57 | $c, |
|---|
| 58 | 'recent' => { |
|---|
| 59 | }, |
|---|
| 60 | ); |
|---|
| 61 | |
|---|
| 62 | # reset counter. |
|---|
| 63 | for my $canon_channel ( sort keys %{ $c->{irc_heap}->{channel_name} } ) { |
|---|
| 64 | $c->{irc_heap}->{unread_lines}->{$canon_channel} = 0; |
|---|
| 65 | $c->{irc_heap}->{channel_recent}->{$canon_channel} = []; |
|---|
| 66 | } |
|---|
| 67 | |
|---|
| 68 | return $out; |
|---|
| 69 | } |
|---|
| 70 | |
|---|
| 71 | # topic on every channel |
|---|
| 72 | sub dispatch_topics { |
|---|
| 73 | my ($class, $c) = @_; |
|---|
| 74 | |
|---|
| 75 | return render( |
|---|
| 76 | $c, |
|---|
| 77 | 'topics' => { }, |
|---|
| 78 | ); |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | sub post_dispatch_show_channel { |
|---|
| 82 | my ( $class, $c, $recent_mode, $channel) = @_; |
|---|
| 83 | |
|---|
| 84 | $channel = decode('utf8', $channel); # maybe $channel is not flagged utf8. |
|---|
| 85 | |
|---|
| 86 | my $r = CGI->new( $c->{req}->content ); |
|---|
| 87 | my $message = $r->param('msg'); |
|---|
| 88 | $message = decode( $c->{config}->{httpd}->{charset}, $message ); |
|---|
| 89 | |
|---|
| 90 | DEBUG "POST MESSAGE $message"; |
|---|
| 91 | |
|---|
| 92 | if ($message) { |
|---|
| 93 | if ($message =~ m{^/}) { |
|---|
| 94 | DEBUG "SENDING COMMAND"; |
|---|
| 95 | $message =~ s!^/!!g; |
|---|
| 96 | |
|---|
| 97 | my @args = |
|---|
| 98 | map { encode( $c->{config}->{irc}->{incode}, $_ ) } split /\s+/, |
|---|
| 99 | $message; |
|---|
| 100 | |
|---|
| 101 | $c->{poe}->kernel->post('mobirc_irc', @args); |
|---|
| 102 | } else { |
|---|
| 103 | DEBUG "NORMAL PRIVMSG"; |
|---|
| 104 | |
|---|
| 105 | $c->{poe}->kernel->post( 'mobirc_irc', |
|---|
| 106 | privmsg => encode( $c->{config}->{irc}->{incode}, $channel ) => |
|---|
| 107 | encode( $c->{config}->{irc}->{incode}, $message ) ); |
|---|
| 108 | |
|---|
| 109 | DEBUG "Sending message $message"; |
|---|
| 110 | add_message( |
|---|
| 111 | $c->{poe}, |
|---|
| 112 | $channel, |
|---|
| 113 | $c->{irc_heap}->{irc}->nick_name, |
|---|
| 114 | $message, |
|---|
| 115 | 'publicfromhttpd', |
|---|
| 116 | ); |
|---|
| 117 | } |
|---|
| 118 | } |
|---|
| 119 | |
|---|
| 120 | my $response = HTTP::Response->new(302); |
|---|
| 121 | $response->push_header( 'Location' => $c->{req}->uri . '?time=' . time); # TODO: must be absoulute url. |
|---|
| 122 | return $response; |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | |
|---|
| 126 | sub dispatch_show_channel { |
|---|
| 127 | my ($class, $c, $recent_mode, $channel) = @_; |
|---|
| 128 | |
|---|
| 129 | DEBUG "show channel page: $channel"; |
|---|
| 130 | $channel = decode('utf8', $channel); # maybe $channel is not flagged utf8. |
|---|
| 131 | |
|---|
| 132 | my $out = render( |
|---|
| 133 | $c, |
|---|
| 134 | 'show_channel' => { |
|---|
| 135 | canon_channel => canon_name($channel), |
|---|
| 136 | channel => $channel, |
|---|
| 137 | subtitle => compact_channel_name($channel), |
|---|
| 138 | recent_mode => $recent_mode, |
|---|
| 139 | } |
|---|
| 140 | ); |
|---|
| 141 | |
|---|
| 142 | { |
|---|
| 143 | my $canon_channel = canon_name($channel); |
|---|
| 144 | |
|---|
| 145 | # clear unread counter |
|---|
| 146 | $c->{irc_heap}->{unread_lines}->{$canon_channel} = 0; |
|---|
| 147 | |
|---|
| 148 | # clear recent messages buffer |
|---|
| 149 | $c->{irc_heap}->{channel_recent}->{$canon_channel} = []; |
|---|
| 150 | } |
|---|
| 151 | |
|---|
| 152 | return $out; |
|---|
| 153 | } |
|---|
| 154 | |
|---|
| 155 | sub render { |
|---|
| 156 | my ( $c, $name, $args ) = @_; |
|---|
| 157 | |
|---|
| 158 | croak "invalid args : $args" unless ref $args eq 'HASH'; |
|---|
| 159 | |
|---|
| 160 | DEBUG "rendering template"; |
|---|
| 161 | |
|---|
| 162 | # set default vars |
|---|
| 163 | $args = { |
|---|
| 164 | compact_channel_name => \&compact_channel_name, |
|---|
| 165 | docroot => $c->{config}->{httpd}->{root}, |
|---|
| 166 | render_line => sub { render_line( $c, @_ ) }, |
|---|
| 167 | user_agent => $c->{user_agent}, |
|---|
| 168 | title => $c->{config}->{httpd}->{title}, |
|---|
| 169 | version => $Mobirc::VERSION, |
|---|
| 170 | |
|---|
| 171 | %{ $c->{irc_heap} }, |
|---|
| 172 | |
|---|
| 173 | %$args, |
|---|
| 174 | }; |
|---|
| 175 | |
|---|
| 176 | my $tt = Template->new( |
|---|
| 177 | ABSOLUTE => 1, |
|---|
| 178 | INCLUDE_PATH => |
|---|
| 179 | File::Spec->catfile( $c->{config}->{global}->{assets_dir}, 'tmpl', ) |
|---|
| 180 | ); |
|---|
| 181 | $tt->process( |
|---|
| 182 | File::Spec->catfile( |
|---|
| 183 | $c->{config}->{global}->{assets_dir}, |
|---|
| 184 | 'tmpl', "$name.html" |
|---|
| 185 | ), |
|---|
| 186 | $args, |
|---|
| 187 | \my $out |
|---|
| 188 | ) or die $tt->error; |
|---|
| 189 | |
|---|
| 190 | DEBUG "rendering done"; |
|---|
| 191 | |
|---|
| 192 | my $content = Encode::is_utf8($out) ? $out : decode( 'utf8', $out ); |
|---|
| 193 | $content = encode($c->{config}->{httpd}->{charset}, $content); |
|---|
| 194 | |
|---|
| 195 | my $response = HTTP::Response->new(200); |
|---|
| 196 | $response->push_header( 'Content-type' => $c->{config}->{httpd}->{content_type} ); |
|---|
| 197 | $response->push_header('Content-Length' => length($content) ); |
|---|
| 198 | |
|---|
| 199 | if ( $c->{config}->{httpd}->{use_cookie} ) { |
|---|
| 200 | set_cookie( $c, $response ); |
|---|
| 201 | } |
|---|
| 202 | |
|---|
| 203 | $response->content( $content ); |
|---|
| 204 | return $response; |
|---|
| 205 | } |
|---|
| 206 | |
|---|
| 207 | sub set_cookie { |
|---|
| 208 | my $c = shift; |
|---|
| 209 | my $response = shift; |
|---|
| 210 | |
|---|
| 211 | my ( $user_info, ) = |
|---|
| 212 | map { $_->{config} } |
|---|
| 213 | first { $_->{module} =~ /Cookie$/ } |
|---|
| 214 | @{ $c->{config}->{httpd}->{authorizer} }; |
|---|
| 215 | croak "Can't get user_info" unless $user_info; |
|---|
| 216 | |
|---|
| 217 | $response->push_header( |
|---|
| 218 | 'Set-Cookie' => CGI::Cookie->new( |
|---|
| 219 | -name => 'username', |
|---|
| 220 | -value => $user_info->{username}, |
|---|
| 221 | -expires => $c->{config}->{httpd}->{cookie_expires} |
|---|
| 222 | ) |
|---|
| 223 | ); |
|---|
| 224 | $response->push_header( |
|---|
| 225 | 'Set-Cookie' => CGI::Cookie->new( |
|---|
| 226 | -name => 'passwd', |
|---|
| 227 | -value => $user_info->{username}, |
|---|
| 228 | -expires => $c->{config}->{httpd}->{cookie_expires} |
|---|
| 229 | ) |
|---|
| 230 | ); |
|---|
| 231 | } |
|---|
| 232 | |
|---|
| 233 | sub render_line { |
|---|
| 234 | my $c = shift; |
|---|
| 235 | my $src = shift; |
|---|
| 236 | |
|---|
| 237 | return "" unless $src; |
|---|
| 238 | croak "must be flagged utf8: $src" unless Encode::is_utf8($src); |
|---|
| 239 | |
|---|
| 240 | $src = encode_entities($src, q(<>&"')); |
|---|
| 241 | |
|---|
| 242 | URI::Find->new( |
|---|
| 243 | sub { |
|---|
| 244 | my ( $uri, $orig_uri ) = @_; |
|---|
| 245 | |
|---|
| 246 | my $out = qq{<a href="$uri" rel="nofollow">$orig_uri</a>}; |
|---|
| 247 | if ( $c->{config}->{httpd}->{au_pcsv} ) { |
|---|
| 248 | $out .= |
|---|
| 249 | sprintf( '<a href="device:pcsiteviewer?url=%s">[PCSV]</a>', |
|---|
| 250 | $uri ); |
|---|
| 251 | } |
|---|
| 252 | $out .= |
|---|
| 253 | sprintf( |
|---|
| 254 | '<a href="http://mgw.hatena.ne.jp/?url=%s&noimage=0&split=1">[ph]</a>', |
|---|
| 255 | uri_escape($uri) ); |
|---|
| 256 | return $out; |
|---|
| 257 | } |
|---|
| 258 | )->find( \$src ); |
|---|
| 259 | |
|---|
| 260 | $src =~ |
|---|
| 261 | s!\b(0\d{1,3})([-(]?)(\d{2,4})([-)]?)(\d{4})\b!<a href="tel:$1$3$5">$1$2$3$4$5</a>!g; |
|---|
| 262 | $src =~ |
|---|
| 263 | s!\b(\w[\w.+=-]+\@[\w.-]+[\w]\.[\w]{2,4})\b!<a href="mailto:$1">$1</a>!g; |
|---|
| 264 | |
|---|
| 265 | $src = decorate_irc_color($src); |
|---|
| 266 | |
|---|
| 267 | $src =~ s{^\*([a-z_]+)\*(\d+):(\d+)\s*(.+)$}{ |
|---|
| 268 | my ($class, $hour, $minute, $body) = ($1, $2, $3, $4); |
|---|
| 269 | |
|---|
| 270 | if ($class eq 'notice' || $class eq 'public') { |
|---|
| 271 | $body =~ s!^([^&]+)> (.+)$!sprintf "<span class='%s'>$1</span>> $2", ($1 eq $c->{irc_heap}->{irc}->nick_name) ? 'nick_myself' : 'nick_normal'!e; |
|---|
| 272 | } |
|---|
| 273 | |
|---|
| 274 | my $res = qq!<span class="time"><span class="hour">$hour</span><span class="colon">:</span><span class="minute">$minute</span></span>!; |
|---|
| 275 | $res .= " "; |
|---|
| 276 | $res .= qq!<span class="$class">$body</span>!; |
|---|
| 277 | $res; |
|---|
| 278 | }e; |
|---|
| 279 | |
|---|
| 280 | return $src; |
|---|
| 281 | } |
|---|
| 282 | |
|---|
| 283 | 1; |
|---|