package Bot::BB3::Roles::PasteBot; use POE; use POE::Component::Server::SimpleHTTP; use HTTP::Status; use CGI; use Template; use strict; our( $INDEX_HTML, $RECEIVED_PASTE_HTML, $DISPLAY_PASTE_HTML ); sub new { my( $class, $conf, $pm ) = @_; my $self = bless { conf => $conf, pm => $pm }, $class; $self->{hostname} = $conf->{roles}->{pastebot}->{hostname} || "127.0.0.1"; $self->{hostname} =~ s{^\s*http:/?/?}{}; # TODO I think this is slightly duplicating the above data # Note that it can default to empty though.. $self->{alias_url} = $conf->{roles}->{pastebot}->{alias_url}; $self->{session} = POE::Session->create( object_states => [ $self => [ qw/_start display_page index display_paste receive_paste/ ] ] ); eval { $self->dbh->do( "CREATE TABLE paste ( paste_id INTEGER PRIMARY KEY AUTOINCREMENT, author VARCHAR(200), summary VARCHAR(250), paste LONGTEXT, date_time INTEGER )" ); }; return $self; } # This method may be called as either a class method or an object method # so we have to have some ugly branches to account for it. sub dbh { my( $self ) = @_; if( ref $self and $self->{dbh} and $self->{dbh}->ping ) { return $self->{dbh}; } my $dbh = DBI->connect( "dbi:SQLite:dbname=var/pastes.db", "", "", {RaiseError => 1, PrintError => 0} ) or die "Failed to create DBI connection to var/pastes.db, this is a Big Problem! $!"; if( ref $self ) { $self->{dbh} = $dbh; } return $dbh; } # This is a public method that can be called as a class method # therefor $self may be a class or an object. sub get_paste { my( $self, $paste_id ) = @_; my $paste = $self->dbh->selectrow_hashref( "SELECT author,summary,paste,date_time FROM paste WHERE paste_id = ? LIMIT 1", undef, $paste_id ); return $paste; } sub insert_paste { my( $self, $nick, $summary, $paste )= @_; my $dbh = $self->dbh; $dbh->do( "INSERT INTO paste (author, summary, paste, date_time) VALUES (?,?,?,?) ", undef, $nick, $summary, $paste, time ); my $id = $dbh->last_insert_id( undef, undef, undef, undef ); return $id; } sub _start { my( $self, $kernel ) = @_[OBJECT,KERNEL]; my $conf = $self->{conf}; # Create it here so it acts as a child $self->{server} = POE::Component::Server::SimpleHTTP->new( PORT => $conf->{pastebot_plugin_port}, ADDRESS => $conf->{pastebot_plugin_addr} || undef, ALIAS => 'pb_httpd_alias', HANDLERS => [ { DIR => '^/paste/\d+', SESSION => "pastebot_role", EVENT => "display_paste", }, { DIR => '^/paste_submit', SESSION => "pastebot_role", EVENT => "receive_paste", }, { DIR => '^/', SESSION => "pastebot_role", EVENT => "index", }, ] ); $kernel->alias_set( "pastebot_role" ); $kernel->sig("DIE" => 'sig_DIE' ); } sub display_page { my( $self, $resp, $html ) = @_[OBJECT,ARG0,ARG1,ARG2]; warn "Display Page Activating: $resp\n"; $resp->code(RC_OK); $resp->content_type("text/html"); $resp->content( $html ); $_[KERNEL]->post( pb_httpd_alias => 'DONE' => $resp ); } sub index { my( $self, $kernel, $req, $resp ) = @_[OBJECT,KERNEL,ARG0,ARG1]; my $template = Template->new; my $channels = $kernel->call("Bot::BB3::Roles::IRC", "channel_list") or warn "Failed to call: $!"; my $context = { channels => $channels, alias_url => $self->{alias_url}, }; my $output_html; $template->process( \$INDEX_HTML, $context, \$output_html ) or warn "Failed to process: $Template::ERROR\n"; $_[KERNEL]->yield( display_page => $resp, $output_html ); } sub display_paste { my( $self, $req, $resp ) = @_[OBJECT,ARG0,ARG1]; my $output_html = "Invalid Paste ID"; $req->uri =~ m{paste/(\d+)} or goto CLEANUP; my $paste_id = $1; my $paste = $self->get_paste( $paste_id ); if( not $paste or not keys %$paste ) { goto CLEANUP; } my $template = Template->new; my $context = { author => $paste->{author}, summary => $paste->{summary}, paste => $paste->{paste}, date_time => $paste->{date_time}, }; undef $output_html; $template->process( \$DISPLAY_PASTE_HTML, $context, \$output_html ); CLEANUP: $_[KERNEL]->yield( display_page => $resp, $output_html ); } sub receive_paste { my( $self, $req, $resp ) = @_[OBJECT,ARG0,ARG1]; warn "Request: ", $req->content; my $query = CGI->new( $req->content ); my $input = $query->param("body"); warn "Attempting to handle request: $req $resp $input\n"; my $id = $self->insert_paste( $query->param("nick"), $query->param("summary"), $query->param("paste"), time ); my $template = Template->new; my $context = { map { $_ => $query->param( $_ ) } qw/nick summary channel paste/, id => $id, alias_url => $self->{alias_url}, }; my $output; $template->process( \$RECEIVED_PASTE_HTML, $context, \$output ) or warn $Template::ERROR; $_[KERNEL]->yield( display_page => $resp, $output ); my $alert_channel = $query->param("channel"); if( $alert_channel !~ /^\s*---/ ) { # Ignore things like "---irc.freenode, skip server names my($server,$nick,$channel) = split /:/,$alert_channel,3; my $external_url = $self->{alias_url} || $self->{hostname}; $_[KERNEL]->post( "Bot::BB3::Roles::IRC", external_message => $server, $nick, $channel, ( $context->{nick} || "Someone" ) . " pasted a new file at $external_url/paste/$id - $context->{summary}" ); } } sub sig_DIE { # Do nothing, we're ignoring fatal errors from our child, poco-server-simplehttp. I think we don't need to respawn them. } $INDEX_HTML = <<'END_HTML';

Welcome to the BB3 Pastebot.

  1. Select the channel for the URL announcment.
  2. Supply a nick for the announcement.
  3. Supply a summary of the paste for the announcement.
  4. Paste!
  5. Submit the form with the Paste it! button.
  1. Channel:
  2. Nick:
  3. Summary:
  4. Paste:
END_HTML $RECEIVED_PASTE_HTML = <<'END_HTML'; Stored as: Paste [% id %]
[% summary %] by [% nick %]
[% paste | html %]
END_HTML $DISPLAY_PASTE_HTML = <<'END_HTML';

BB3 PasteBot

From [% author | html %]

[% summary | html %]

[% paste | html %]
END_HTML 1;