mirror of
https://github.com/pragma-/pbot.git
synced 2024-12-23 11:12:42 +01:00
Progress on refactoring and polishing everything
More to come!
This commit is contained in:
parent
6443c96f09
commit
613890707a
@ -16,6 +16,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use feature 'switch';
|
||||
no if $] >= 5.018, warnings => "experimental::smartmatch";
|
||||
|
@ -12,6 +12,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use feature 'switch';
|
||||
no if $] >= 5.018, warnings => "experimental::smartmatch";
|
||||
|
@ -15,6 +15,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::HiRes qw/gettimeofday/;
|
||||
use Time::Duration;
|
||||
|
@ -12,6 +12,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use feature 'switch';
|
||||
no if $] >= 5.018, warnings => "experimental::smartmatch";
|
||||
|
@ -11,6 +11,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use feature 'switch';
|
||||
no if $] >= 5.018, warnings => "experimental::smartmatch";
|
||||
|
@ -13,6 +13,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::Duration;
|
||||
use Time::HiRes qw/gettimeofday/;
|
||||
|
@ -13,6 +13,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use PBot::ChanOpCommands;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
@ -12,6 +12,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
|
@ -14,6 +14,7 @@ use parent 'PBot::Class', 'PBot::Registerable';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::Duration qw/duration/;
|
||||
|
||||
|
@ -18,6 +18,7 @@ package PBot::DualIndexHashObject;
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Text::Levenshtein qw(fastdistance);
|
||||
use JSON;
|
||||
|
@ -13,6 +13,7 @@ package PBot::DualIndexSQLiteObject;
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use DBI;
|
||||
use Text::Levenshtein qw(fastdistance);
|
||||
|
@ -7,6 +7,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use IO::Select;
|
||||
|
||||
|
@ -13,6 +13,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::Duration;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
@ -12,6 +12,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use feature 'switch';
|
||||
no if $] >= 5.018, warnings => "experimental::smartmatch";
|
||||
|
@ -24,6 +24,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
|
@ -14,6 +14,7 @@ package PBot::HashObject;
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Text::Levenshtein qw(fastdistance);
|
||||
use JSON;
|
||||
|
@ -23,6 +23,7 @@ use IO::Select;
|
||||
use Carp;
|
||||
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
# grab the drop-in replacement for time() from Time::HiRes, if it's available
|
||||
BEGIN { Time::HiRes->import('time') if eval "require Time::HiRes"; }
|
||||
|
@ -16,6 +16,7 @@
|
||||
package PBot::IRC::Connection; # pragma_ 2011/21/01
|
||||
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use PBot::IRC::Event; # pragma_ 2011/21/01
|
||||
use PBot::IRC::DCC; # pragma_ 2011/21/01
|
||||
@ -221,8 +222,8 @@ sub connect {
|
||||
if (@_) {
|
||||
my (%arg) = @_;
|
||||
|
||||
$self->hostname($arg{'LocalAddr'}) if exists $arg{'LocalAddr'};
|
||||
$password = $arg{'Password'} if exists $arg{'Password'};
|
||||
$self->hostname($arg{'LocalAddr'}) if exists $arg{'LocalAddr'};
|
||||
$password = $arg{'Password'} if exists $arg{'Password'};
|
||||
$self->nick($arg{'Nick'}) if exists $arg{'Nick'};
|
||||
$self->port($arg{'Port'}) if exists $arg{'Port'};
|
||||
$self->server($arg{'Server'}) if exists $arg{'Server'};
|
||||
|
@ -18,6 +18,7 @@ package PBot::IRC::DCC; # pragma_ 2011/21/01
|
||||
use strict;
|
||||
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
# --- #perl was here! ---
|
||||
#
|
||||
|
@ -25,6 +25,7 @@
|
||||
package PBot::IRC::Event; # pragma_ 2011/21/01
|
||||
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use strict;
|
||||
our %_names;
|
||||
|
@ -1,6 +1,7 @@
|
||||
package PBot::IRC::EventQueue; # pragma_ 2011/21/01
|
||||
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use PBot::IRC::EventQueue::Entry; # pragma_ 2011/21/01
|
||||
|
||||
|
@ -3,6 +3,7 @@ package PBot::IRC::EventQueue::Entry; # pragma_ 2011/21/01
|
||||
use strict;
|
||||
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
my $id = 0;
|
||||
|
||||
|
@ -13,6 +13,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
use Data::Dumper;
|
||||
|
@ -12,6 +12,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::Duration qw/concise duration/;
|
||||
|
||||
|
@ -13,6 +13,7 @@ use parent 'PBot::Class', 'PBot::Registerable';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::HiRes qw/gettimeofday/;
|
||||
use Time::Duration;
|
||||
@ -30,8 +31,8 @@ sub initialize {
|
||||
}
|
||||
|
||||
sub process_line {
|
||||
my $self = shift;
|
||||
my ($from, $nick, $user, $host, $text) = @_;
|
||||
my ($self, $from, $nick, $user, $host, $text) = @_;
|
||||
|
||||
$from = lc $from if defined $from;
|
||||
|
||||
my $context = {from => $from, nick => $nick, user => $user, host => $host, hostmask => "$nick!$user\@$host", text => $text};
|
||||
@ -737,37 +738,58 @@ sub lc_args {
|
||||
}
|
||||
|
||||
sub truncate_result {
|
||||
my ($self, $from, $nick, $text, $original_result, $result, $paste) = @_;
|
||||
my ($self, $from, $nick, $command, $paste_text, $text, $paste) = @_;
|
||||
|
||||
my $max_msg_len = $self->{pbot}->{registry}->get_value('irc', 'max_msg_len');
|
||||
|
||||
$max_msg_len -= length "PRIVMSG $from :" if defined $from;
|
||||
|
||||
utf8::encode $result;
|
||||
utf8::encode $original_result;
|
||||
utf8::encode $paste_text;
|
||||
utf8::encode $text;
|
||||
|
||||
use bytes;
|
||||
if (length $text > $max_msg_len) {
|
||||
my $paste_result;
|
||||
|
||||
if (length $result > $max_msg_len) {
|
||||
my $link;
|
||||
if ($paste) {
|
||||
# limit pastes to 32k by default, overridable via paste.max_length
|
||||
my $max_paste_len = $self->{pbot}->{registry}->get_value('paste', 'max_length') // 1024 * 32;
|
||||
$original_result = substr $original_result, 0, $max_paste_len;
|
||||
$link = $self->{pbot}->{webpaste}->paste("[" . (defined $from ? $from : "stdin") . "] <$nick> $text\n\n$original_result");
|
||||
} else {
|
||||
$link = 'undef';
|
||||
|
||||
# truncate paste to max paste length
|
||||
# FIXME: this potentially chops unicode characters in wrong places
|
||||
$paste_text = substr $paste_text, 0, $max_paste_len;
|
||||
|
||||
utf8::decode $paste_text;
|
||||
|
||||
# send text to paste site
|
||||
$paste_result = $self->{pbot}->{webpaste}->paste("($from) $nick: $command\n\n$paste_text");
|
||||
}
|
||||
|
||||
my $trunc = "... [truncated; ";
|
||||
if ($link =~ m/^http/) { $trunc .= "see $link for full text.]"; }
|
||||
else { $trunc .= "$link]"; }
|
||||
my $trunc = "... [truncated";
|
||||
|
||||
$self->{pbot}->{logger}->log("Message truncated -- pasted to $link\n") if $paste;
|
||||
my $trunc_len = length $result < $max_msg_len ? length $result : $max_msg_len;
|
||||
$result = substr($result, 0, $trunc_len);
|
||||
substr($result, $trunc_len - length $trunc) = $trunc;
|
||||
if (not defined $paste_result) {
|
||||
# no paste
|
||||
$trunc .= "]";
|
||||
} elsif ($paste_result =~ m/^http/) {
|
||||
# a link
|
||||
$trunc .= "; see $paste_result for full text.]";
|
||||
} else {
|
||||
# an error or something else
|
||||
$trunc .= "$paste_result]";
|
||||
}
|
||||
|
||||
if ($paste) {
|
||||
$paste_result //= 'not pasted';
|
||||
$self->{pbot}->{logger}->log("Message truncated -- $paste_result\n");
|
||||
}
|
||||
|
||||
my $trunc_len = length $text < $max_msg_len ? length $text : $max_msg_len;
|
||||
# FIXME: this potentially chops unicode characters in wrong places
|
||||
$text = substr($text, 0, $trunc_len);
|
||||
substr($text, $trunc_len - length $trunc) = $trunc;
|
||||
}
|
||||
|
||||
utf8::decode $result;
|
||||
return $result;
|
||||
utf8::decode $text;
|
||||
return $text;
|
||||
}
|
||||
|
||||
sub handle_result {
|
||||
@ -839,8 +861,6 @@ sub handle_result {
|
||||
$result = $context->{split_result} . $result;
|
||||
}
|
||||
|
||||
my $original_result = $result;
|
||||
|
||||
my $use_output_queue = 0;
|
||||
|
||||
if (defined $context->{command}) {
|
||||
@ -854,26 +874,25 @@ sub handle_result {
|
||||
$context->{preserve_whitespace} = $self->{pbot}->{factoids}->{factoids}->get_data($chan, $trigger, 'preserve_whitespace') // 0;
|
||||
}
|
||||
|
||||
$use_output_queue = $self->{pbot}->{factoids}->{factoids}->get_data($chan, $trigger, 'use_output_queue');
|
||||
$use_output_queue = 0 if not defined $use_output_queue;
|
||||
$use_output_queue = $self->{pbot}->{factoids}->{factoids}->get_data($chan, $trigger, 'use_output_queue') // 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $preserve_newlines = $self->{pbot}->{registry}->get_value($context->{from}, 'preserve_newlines');
|
||||
|
||||
my $original_result = $result;
|
||||
|
||||
$result =~ s/[\n\r]/ /g unless $preserve_newlines;
|
||||
$result =~ s/[ \t]+/ /g unless $context->{preserve_whitespace};
|
||||
|
||||
my $max_lines = $self->{pbot}->{registry}->get_value($context->{from}, 'max_newlines');
|
||||
$max_lines = 4 if not defined $max_lines;
|
||||
my $max_lines = $self->{pbot}->{registry}->get_value($context->{from}, 'max_newlines') // 4;
|
||||
my $lines = 0;
|
||||
|
||||
my $stripped_line;
|
||||
foreach my $line (split /[\n\r]+/, $result) {
|
||||
$stripped_line = $line;
|
||||
$stripped_line =~ s/^\s+//;
|
||||
$stripped_line =~ s/\s+$//;
|
||||
$stripped_line =~ s/^\s+|\s+$//g;
|
||||
next if not length $stripped_line;
|
||||
|
||||
if (++$lines >= $max_lines) {
|
||||
@ -891,8 +910,11 @@ sub handle_result {
|
||||
last;
|
||||
}
|
||||
|
||||
if ($preserve_newlines) { $line = $self->truncate_result($context->{from}, $context->{nick}, $context->{text}, $line, $line, 1); }
|
||||
else { $line = $self->truncate_result($context->{from}, $context->{nick}, $context->{text}, $original_result, $line, 1); }
|
||||
if ($preserve_newlines) {
|
||||
$line = $self->truncate_result($context->{from}, $context->{nick}, $context->{text}, $line, $line, 1);
|
||||
} else {
|
||||
$line = $self->truncate_result($context->{from}, $context->{nick}, $context->{text}, $original_result, $line, 1);
|
||||
}
|
||||
|
||||
if ($use_output_queue) {
|
||||
my $delay = rand(10) + 5;
|
||||
|
@ -14,6 +14,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::HiRes qw(gettimeofday tv_interval);
|
||||
use Time::Duration;
|
||||
|
@ -6,6 +6,7 @@ package PBot::Logger;
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Scalar::Util qw/openhandle/;
|
||||
use File::Basename;
|
||||
@ -24,36 +25,61 @@ sub new {
|
||||
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
$self->{logfile} = $conf{filename} // Carp::croak "Missing logfile parameter in " . __FILE__;
|
||||
$self->{start} = time;
|
||||
|
||||
# ensure logfile path was provided
|
||||
$self->{logfile} = $conf{filename} // Carp::croak "Missing logfile parameter in " . __FILE__;
|
||||
|
||||
|
||||
# record start time for later logfile rename in rotation
|
||||
$self->{start} = time;
|
||||
|
||||
# get directories leading to logfile
|
||||
my $path = dirname $self->{logfile};
|
||||
|
||||
# create log file path
|
||||
if (not -d $path) {
|
||||
print "Creating new logfile path: $path\n" unless $self->{pbot}->{overrides}->{'general.daemon'};
|
||||
mkdir $path or Carp::croak "Couldn't create logfile path: $!\n";
|
||||
}
|
||||
|
||||
open LOGFILE, ">>$self->{logfile}" or Carp::croak "Couldn't open logfile $self->{logfile}: $!\n";
|
||||
# open log file with utf8 encoding
|
||||
open LOGFILE, ">> :encoding(UTF-8)", $self->{logfile} or Carp::croak "Couldn't open logfile $self->{logfile}: $!\n";
|
||||
LOGFILE->autoflush(1);
|
||||
|
||||
# rename logfile to start-time at exit
|
||||
$self->{pbot}->{atexit}->register(sub { $self->rotate_log; return; });
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub log {
|
||||
my ($self, $text) = @_;
|
||||
|
||||
# get current time
|
||||
my $time = localtime;
|
||||
|
||||
# replace potentially log-corrupting characters (colors, gibberish, etc)
|
||||
$text =~ s/(\P{PosixGraph})/my $ch = $1; if ($ch =~ m{[\s]}) { $ch } else { sprintf "\\x%02X", ord $ch }/ge;
|
||||
|
||||
# log to file
|
||||
print LOGFILE "$time :: $text" if openhandle * LOGFILE;
|
||||
print "$time :: $text" unless $self->{pbot}->{overrides}->{'general.daemon'};
|
||||
|
||||
# and print to stdout unless daemonized
|
||||
print STDOUT "$time :: $text" unless $self->{pbot}->{overrides}->{'general.daemon'};
|
||||
}
|
||||
|
||||
sub rotate_log {
|
||||
my ($self) = @_;
|
||||
|
||||
# get start time
|
||||
my $time = localtime $self->{start};
|
||||
$time =~ s/\s+/_/g;
|
||||
$time =~ s/\s+/_/g; # replace spaces with underscores
|
||||
|
||||
$self->log("Rotating log to $self->{logfile}-$time\n");
|
||||
|
||||
# rename log to start time
|
||||
move($self->{logfile}, $self->{logfile} . '-' . $time);
|
||||
|
||||
# set new start time for next rotation
|
||||
$self->{start} = time;
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -17,6 +17,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Getopt::Long qw(GetOptionsFromArray);
|
||||
use Time::HiRes qw(gettimeofday tv_interval);
|
||||
|
@ -13,6 +13,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use DBI;
|
||||
use Carp qw(shortmess);
|
||||
|
@ -13,6 +13,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::Duration qw/duration/;
|
||||
|
||||
|
@ -1,6 +1,12 @@
|
||||
# File: Modules.pm
|
||||
# Author: pragma_
|
||||
|
||||
# Purpose: Modules are command-line programs and scripts that can be loaded
|
||||
# via PBot factoids. Command arguments are passed as command-line arguments.
|
||||
# The standard output from the script is returned as the bot command result.
|
||||
# The standard error output is stored in a file named <module>-stderr in the
|
||||
# modules/ directory.
|
||||
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
@ -11,43 +17,65 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use IPC::Run qw/run timeout/;
|
||||
use Encode;
|
||||
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
|
||||
# bot commands to load and unload modules
|
||||
$self->{pbot}->{commands}->register(sub { $self->cmd_load(@_) }, "load", 1);
|
||||
$self->{pbot}->{commands}->register(sub { $self->cmd_unload(@_) }, "unload", 1);
|
||||
}
|
||||
|
||||
sub cmd_load {
|
||||
my ($self, $context) = @_;
|
||||
my $factoids = $self->{pbot}->{factoids}->{factoids};
|
||||
|
||||
my ($keyword, $module) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 2);
|
||||
|
||||
return "Usage: load <keyword> <module>" if not defined $module;
|
||||
|
||||
if ($factoids->exists('.*', $keyword)) { return 'There is already a keyword named ' . $factoids->get_data('.*', $keyword, '_name') . '.'; }
|
||||
my $factoids = $self->{pbot}->{factoids}->{factoids};
|
||||
|
||||
if ($factoids->exists('.*', $keyword)) {
|
||||
return 'There is already a keyword named ' . $factoids->get_data('.*', $keyword, '_name') . '.';
|
||||
}
|
||||
|
||||
$self->{pbot}->{factoids}->add_factoid('module', '.*', $context->{hostmask}, $keyword, $module, 1);
|
||||
$factoids->set('.*', $keyword, 'add_nick', 1, 1);
|
||||
|
||||
$factoids->set('.*', $keyword, 'add_nick', 1, 1);
|
||||
$factoids->set('.*', $keyword, 'nooverride', 1);
|
||||
|
||||
$self->{pbot}->{logger}->log("$context->{hostmask} loaded module $keyword => $module\n");
|
||||
|
||||
return "Loaded module $keyword => $module";
|
||||
}
|
||||
|
||||
sub cmd_unload {
|
||||
my ($self, $context) = @_;
|
||||
my $module = $self->{pbot}->{interpreter}->shift_arg($context->{arglist});
|
||||
return "Usage: unload <keyword>" if not defined $module;
|
||||
my $factoids = $self->{pbot}->{factoids}->{factoids};
|
||||
return "/say $module not found." if not $factoids->exists('.*', $module);
|
||||
|
||||
if ($factoids->get_data('.*', $module, 'type') ne 'module') { return "/say " . $factoids->get_data('.*', $module, '_name') . ' is not a module.'; }
|
||||
my $module = $self->{pbot}->{interpreter}->shift_arg($context->{arglist});
|
||||
|
||||
return "Usage: unload <keyword>" if not defined $module;
|
||||
|
||||
my $factoids = $self->{pbot}->{factoids}->{factoids};
|
||||
|
||||
if (not $factoids->exists('.*', $module)) {
|
||||
return "/say $module not found.";
|
||||
}
|
||||
|
||||
if ($factoids->get_data('.*', $module, 'type') ne 'module') {
|
||||
return "/say " . $factoids->get_data('.*', $module, '_name') . ' is not a module.';
|
||||
}
|
||||
|
||||
my $name = $factoids->get_data('.*', $module, '_name');
|
||||
|
||||
$factoids->remove('.*', $module);
|
||||
|
||||
$self->{pbot}->{logger}->log("$context->{hostmask} unloaded module $module\n");
|
||||
|
||||
return "/say $name unloaded.";
|
||||
}
|
||||
|
||||
@ -67,8 +95,11 @@ sub execute_module {
|
||||
|
||||
sub launch_module {
|
||||
my ($self, $context) = @_;
|
||||
$context->{arguments} = "" if not defined $context->{arguments};
|
||||
|
||||
$context->{arguments} //= '';
|
||||
|
||||
my @factoids = $self->{pbot}->{factoids}->find_factoid($context->{from}, $context->{keyword}, exact_channel => 2, exact_trigger => 2);
|
||||
|
||||
if (not @factoids or not $factoids[0]) {
|
||||
$context->{checkflood} = 1;
|
||||
$self->{pbot}->{interpreter}->handle_result($context, "/msg $context->{nick} Failed to find module for '$context->{keyword}' in channel $context->{from}\n");
|
||||
@ -76,19 +107,22 @@ sub launch_module {
|
||||
}
|
||||
|
||||
my ($channel, $trigger) = ($factoids[0]->[0], $factoids[0]->[1]);
|
||||
|
||||
$context->{channel} = $channel;
|
||||
$context->{keyword} = $trigger;
|
||||
$context->{trigger} = $trigger;
|
||||
|
||||
my $module = $self->{pbot}->{factoids}->{factoids}->get_data($channel, $trigger, 'action');
|
||||
|
||||
$self->{pbot}->{logger}->log(
|
||||
"(" . (defined $context->{from} ? $context->{from} : "(undef)")
|
||||
. "): $context->{nick}!$context->{user}\@$context->{host}: Executing module [$context->{command}] $module $context->{arguments}\n"
|
||||
'(' . (defined $context->{from} ? $context->{from} : "(undef)") . '): '
|
||||
. "$context->{hostmask}: Executing module [$context->{command}] $module $context->{arguments}\n"
|
||||
);
|
||||
|
||||
$context->{arguments} = $self->{pbot}->{factoids}->expand_factoid_vars($context, $context->{arguments});
|
||||
|
||||
my $module_dir = $self->{pbot}->{registry}->get_value('general', 'module_dir');
|
||||
|
||||
if (not chdir $module_dir) {
|
||||
$self->{pbot}->{logger}->log("Could not chdir to '$module_dir': $!\n");
|
||||
Carp::croak("Could not chdir to '$module_dir': $!");
|
||||
@ -99,16 +133,29 @@ sub launch_module {
|
||||
}
|
||||
|
||||
# FIXME -- add check to ensure $module exists
|
||||
|
||||
my ($exitval, $stdout, $stderr) = eval {
|
||||
my $args = $context->{arguments};
|
||||
if (not $context->{args_utf8}) { $args = encode('UTF-8', $args); }
|
||||
|
||||
if (not $context->{args_utf8}) {
|
||||
$args = encode('UTF-8', $args);
|
||||
}
|
||||
|
||||
my @cmdline = ("./$module", $self->{pbot}->{interpreter}->split_line($args));
|
||||
|
||||
my $timeout = $self->{pbot}->{registry}->get_value('general', 'module_timeout') // 30;
|
||||
|
||||
my ($stdin, $stdout, $stderr);
|
||||
|
||||
run \@cmdline, \$stdin, \$stdout, \$stderr, timeout($timeout);
|
||||
|
||||
my $exitval = $? >> 8;
|
||||
utf8::decode($stdout);
|
||||
utf8::decode($stderr);
|
||||
|
||||
$self->{pbot}->{logger}->log("stdout before: $stdout\n");
|
||||
|
||||
utf8::decode $stdout;
|
||||
utf8::decode $stderr;
|
||||
|
||||
return ($exitval, $stdout, $stderr);
|
||||
};
|
||||
|
||||
@ -118,6 +165,7 @@ sub launch_module {
|
||||
($exitval, $stdout, $stderr) = (-1, "$context->{trigger}: timed-out", '');
|
||||
} else {
|
||||
($exitval, $stdout, $stderr) = (-1, '', $error);
|
||||
$self->{pbot}->{logger}->log("$context->{trigger}: error executing module: $error\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Text::Levenshtein qw/fastdistance/;
|
||||
use Data::Dumper;
|
||||
|
14
PBot/PBot.pm
14
PBot/PBot.pm
@ -13,9 +13,6 @@ use strict; use warnings;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
# unbuffer stdout
|
||||
STDOUT->autoflush(1);
|
||||
|
||||
use Carp ();
|
||||
use PBot::Logger;
|
||||
use PBot::VERSION;
|
||||
@ -54,6 +51,17 @@ use PBot::Users;
|
||||
use PBot::Utils::ParseDate;
|
||||
use PBot::WebPaste;
|
||||
|
||||
# unbuffer stdout stream
|
||||
STDOUT->autoflush(1);
|
||||
|
||||
# set standard output streams to encode as utf8
|
||||
binmode(STDOUT, ":utf8");
|
||||
binmode(STDERR, ":utf8");
|
||||
|
||||
# decode command-line arguments from utf8
|
||||
use Encode;
|
||||
@ARGV = map { decode('UTF-8', $_, 1) } @ARGV;
|
||||
|
||||
sub new {
|
||||
my ($proto, %conf) = @_;
|
||||
my $class = ref($proto) || $proto;
|
||||
|
@ -12,6 +12,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use File::Basename;
|
||||
|
||||
|
@ -13,6 +13,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::Duration qw/concise duration/;
|
||||
use Time::HiRes qw/gettimeofday/;
|
||||
@ -22,9 +23,15 @@ use JSON;
|
||||
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
|
||||
# process manager bot commands
|
||||
$self->{pbot}->{commands}->register(sub { $self->cmd_ps(@_) }, 'ps', 0);
|
||||
$self->{pbot}->{commands}->register(sub { $self->cmd_kill(@_) }, 'kill', 1);
|
||||
|
||||
# give admin capability group the can-kill capability
|
||||
$self->{pbot}->{capabilities}->add('admin', 'can-kill', 1);
|
||||
|
||||
# hash of currently running bot-invoked processes
|
||||
$self->{processes} = {};
|
||||
|
||||
# automatically reap children processes in background
|
||||
@ -35,6 +42,7 @@ sub initialize {
|
||||
|
||||
sub cmd_ps {
|
||||
my ($self, $context) = @_;
|
||||
|
||||
my $usage = 'Usage: ps [-atu]; -a show all information; -t show running time; -u show user/channel';
|
||||
|
||||
my $getopt_error;
|
||||
@ -46,44 +54,55 @@ sub cmd_ps {
|
||||
Getopt::Long::Configure("bundling");
|
||||
|
||||
my ($show_all, $show_user, $show_running_time);
|
||||
|
||||
my @opt_args = $self->{pbot}->{interpreter}->split_line($context->{arguments}, strip_quotes => 1);
|
||||
|
||||
GetOptionsFromArray(
|
||||
\@opt_args,
|
||||
'all|a' => \$show_all,
|
||||
'all|a' => \$show_all,
|
||||
'user|u' => \$show_user,
|
||||
'time|t' => \$show_running_time
|
||||
);
|
||||
|
||||
return "$getopt_error; $usage" if defined $getopt_error;
|
||||
|
||||
my @processes;
|
||||
foreach my $pid (sort keys %{$self->{processes}}) { push @processes, $self->{processes}->{$pid}; }
|
||||
if (not @processes) { return "No running processes."; }
|
||||
|
||||
my $result;
|
||||
if (@processes == 1) { $result = 'One process: '; } else { $result = @processes . ' processes: '; }
|
||||
foreach my $pid (sort keys %{$self->{processes}}) {
|
||||
push @processes, $self->{processes}->{$pid};
|
||||
}
|
||||
|
||||
if (not @processes) {
|
||||
return "No running processes.";
|
||||
}
|
||||
|
||||
my $result = @processes == 1 ? 'One process: ' : @processes . ' processes: ';
|
||||
|
||||
my @entries;
|
||||
|
||||
my $sep = '';
|
||||
foreach my $process (@processes) {
|
||||
$result .= $sep;
|
||||
$result .= "$process->{pid}: $process->{commands}->[0]";
|
||||
my $entry = "$process->{pid}: $process->{commands}->[0]";
|
||||
|
||||
if ($show_running_time or $show_all) {
|
||||
my $duration = concise duration (gettimeofday - $process->{process_start});
|
||||
$result .= " [$duration]";
|
||||
$entry .= " [$duration]";
|
||||
}
|
||||
|
||||
if ($show_user or $show_all) {
|
||||
$result .= " ($process->{nick} in $process->{from})";
|
||||
$entry .= " ($process->{nick} in $process->{from})";
|
||||
}
|
||||
|
||||
$sep = '; ';
|
||||
push @entries, $entry;
|
||||
}
|
||||
|
||||
$result .= join '; ', @entries;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub cmd_kill {
|
||||
my ($self, $context) = @_;
|
||||
|
||||
my $usage = 'Usage: kill [-a] [-t <seconds>] [-s <signal>] [pids...]; -a kill all processes; -t <seconds> kill processes running longer than <seconds>; -s send <signal> to processes';
|
||||
|
||||
my $getopt_error;
|
||||
@ -95,15 +114,21 @@ sub cmd_kill {
|
||||
Getopt::Long::Configure("bundling");
|
||||
|
||||
my ($kill_all, $kill_time, $signal);
|
||||
|
||||
my @opt_args = $self->{pbot}->{interpreter}->split_line($context->{arguments}, preserve_escapes => 1, strip_quotes => 1);
|
||||
|
||||
GetOptionsFromArray(
|
||||
\@opt_args,
|
||||
'all|a' => \$kill_all,
|
||||
'time|t=i' => \$kill_time,
|
||||
'all|a' => \$kill_all,
|
||||
'time|t=i' => \$kill_time,
|
||||
'signal|s=s' => \$signal,
|
||||
);
|
||||
|
||||
return "$getopt_error; $usage" if defined $getopt_error;
|
||||
return "Must specify PIDs to kill unless options -a or -t are provided." if not $kill_all and not $kill_time and not @opt_args;
|
||||
|
||||
if (not $kill_all and not $kill_time and not @opt_args) {
|
||||
return "Must specify PIDs to kill unless options -a or -t are provided.";
|
||||
}
|
||||
|
||||
if (defined $signal) {
|
||||
$signal = uc $signal;
|
||||
@ -112,8 +137,10 @@ sub cmd_kill {
|
||||
}
|
||||
|
||||
my @pids;
|
||||
|
||||
if (defined $kill_all or defined $kill_time) {
|
||||
my $now = time;
|
||||
|
||||
foreach my $pid (sort keys %{$self->{processes}}) {
|
||||
my $process = $self->{processes}->{$pid};
|
||||
next if defined $kill_time and $now - $process->{process_start} < $kill_time;
|
||||
@ -125,27 +152,41 @@ sub cmd_kill {
|
||||
push @pids, $pid;
|
||||
}
|
||||
}
|
||||
|
||||
return "No matching process." if not @pids;
|
||||
|
||||
my $ret = eval { kill $signal, @pids };
|
||||
if ($@) { my $error = $@; $error =~ s/ at PBot.*//; return $error; }
|
||||
|
||||
if ($@) {
|
||||
my $error = $@;
|
||||
$error =~ s/ at PBot.*//;
|
||||
return $error;
|
||||
}
|
||||
|
||||
return "[$ret] Sent signal " . $signal . ' to ' . join ', ', @pids;
|
||||
}
|
||||
|
||||
sub add_process {
|
||||
my ($self, $pid, $context) = @_;
|
||||
|
||||
$context->{process_start} = gettimeofday;
|
||||
|
||||
$self->{processes}->{$pid} = $context;
|
||||
|
||||
$self->{pbot}->{logger}->log("Starting process $pid: $context->{commands}->[0]\n");
|
||||
}
|
||||
|
||||
sub remove_process {
|
||||
my ($self, $pid) = @_;
|
||||
|
||||
if (exists $self->{processes}->{$pid}) {
|
||||
my $command = $self->{processes}->{$pid}->{commands}->[0];
|
||||
|
||||
my $duration = gettimeofday - $self->{processes}->{$pid}->{process_start};
|
||||
$duration = sprintf "%0.3f", $duration;
|
||||
|
||||
$self->{pbot}->{logger}->log("Finished process $pid ($command): duration $duration seconds\n");
|
||||
|
||||
delete $self->{processes}->{$pid};
|
||||
} else {
|
||||
$self->{pbot}->{logger}->log("Finished process $pid\n");
|
||||
@ -154,32 +195,46 @@ sub remove_process {
|
||||
|
||||
sub execute_process {
|
||||
my ($self, $context, $subref, $timeout) = @_;
|
||||
$timeout //= 30;
|
||||
|
||||
if (not exists $context->{commands}) { $context->{commands} = [$context->{command}]; }
|
||||
$timeout //= 30; # default timeout 30 seconds
|
||||
|
||||
if (not exists $context->{commands}) {
|
||||
$context->{commands} = [$context->{command}];
|
||||
}
|
||||
|
||||
# don't fork again if we're already a forked process
|
||||
if (exists $context->{pid}) {
|
||||
if (defined $context->{pid} and $context->{pid} == 0) {
|
||||
$subref->($context);
|
||||
return $context->{result};
|
||||
}
|
||||
|
||||
pipe(my $reader, my $writer);
|
||||
|
||||
# fork new process
|
||||
$context->{pid} = fork;
|
||||
|
||||
if (not defined $context->{pid}) {
|
||||
# fork failed
|
||||
$self->{pbot}->{logger}->log("Could not fork process: $!\n");
|
||||
|
||||
close $reader;
|
||||
close $writer;
|
||||
|
||||
delete $context->{pid};
|
||||
|
||||
# groan to let the users know something went wrong
|
||||
$context->{checkflood} = 1;
|
||||
$self->{pbot}->{interpreter}->handle_result($context, "/me groans loudly.\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($context->{pid} == 0) {
|
||||
# child
|
||||
|
||||
close $reader;
|
||||
|
||||
# flag this instance as child
|
||||
$self->{pbot}->{child} = 1;
|
||||
|
||||
# don't quit the IRC client when the child dies
|
||||
@ -190,23 +245,31 @@ sub execute_process {
|
||||
# remove atexit handlers
|
||||
$self->{pbot}->{atexit}->unregister_all;
|
||||
|
||||
# FIXME: close databases and files too? Or just set everything to check for $self->{pbot}->{child} == 1 or $context->{pid} == 0?
|
||||
|
||||
# execute the provided subroutine, results are stored in $context
|
||||
eval {
|
||||
local $SIG{ALRM} = sub { die "Process `$context->{commands}->[0]` timed-out" };
|
||||
alarm $timeout;
|
||||
$subref->($context);
|
||||
};
|
||||
alarm 0;
|
||||
|
||||
# check for errors
|
||||
if ($@) {
|
||||
$context->{result} = $@;
|
||||
|
||||
$context->{'timed-out'} = 1 if $context->{result} =~ /^Process .* timed-out at PBot\/ProcessManager/;
|
||||
|
||||
$self->{pbot}->{logger}->log("Error executing process: $context->{result}\n");
|
||||
|
||||
# strip internal PBot source data for IRC output
|
||||
$context->{result} =~ s/ at PBot.*$//ms;
|
||||
$context->{result} =~ s/\s+...propagated at .*$//ms;
|
||||
}
|
||||
|
||||
# turn alarm back on for PBot::Timer
|
||||
alarm 1;
|
||||
|
||||
# print $context to pipe
|
||||
my $json = encode_json $context;
|
||||
print $writer "$json\n";
|
||||
@ -216,46 +279,62 @@ sub execute_process {
|
||||
exit 0;
|
||||
} else {
|
||||
# parent
|
||||
|
||||
# nothing to write to child
|
||||
close $writer;
|
||||
|
||||
# add process
|
||||
$self->add_process($context->{pid}, $context);
|
||||
|
||||
# add reader handler
|
||||
$self->{pbot}->{select_handler}->add_reader($reader, sub { $self->process_pipe_reader($context->{pid}, @_) });
|
||||
|
||||
# return empty string since reader will handle the output when child is finished
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
sub process_pipe_reader {
|
||||
my ($self, $pid, $buf) = @_;
|
||||
|
||||
# retrieve context object from child
|
||||
my $context = decode_json $buf or do {
|
||||
$self->{pbot}->{logger}->log("Failed to decode bad json: [$buf]\n");
|
||||
return;
|
||||
};
|
||||
|
||||
# context is no longer forked
|
||||
delete $context->{pid};
|
||||
|
||||
# check for output
|
||||
if (not defined $context->{result} or not length $context->{result}) {
|
||||
$self->{pbot}->{logger}->log("No result from process.\n");
|
||||
return if $context->{suppress_no_output};
|
||||
$context->{result} = "No output.";
|
||||
}
|
||||
|
||||
if ($context->{referenced}) { return if $context->{result} =~ m/(?:no results)/i; }
|
||||
|
||||
if (exists $context->{special} and $context->{special} eq 'code-factoid') {
|
||||
$context->{result} =~ s/\s+$//g;
|
||||
$self->{pbot}->{logger}->log("No text result from code-factoid.\n") and return if not length $context->{result};
|
||||
$context->{original_keyword} = $context->{root_keyword};
|
||||
$context->{result} = $self->{pbot}->{factoids}->handle_action($context, $context->{result});
|
||||
# don't output unnecessary result if command was referenced within a message
|
||||
if ($context->{referenced}) {
|
||||
return if $context->{result} =~ m/(?:no results)/i;
|
||||
}
|
||||
|
||||
$context->{checkflood} = 0;
|
||||
# handle code factoid result
|
||||
if (exists $context->{special} and $context->{special} eq 'code-factoid') {
|
||||
$context->{result} =~ s/\s+$//g;
|
||||
|
||||
if (defined $context->{nickoverride}) { $self->{pbot}->{interpreter}->handle_result($context, $context->{result}); }
|
||||
else {
|
||||
# don't override nick if already set
|
||||
if ( exists $context->{special}
|
||||
and $context->{special} ne 'code-factoid'
|
||||
if (not length $context->{result}) {
|
||||
$self->{pbot}->{logger}->log("No text result from code-factoid.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$context->{original_keyword} = $context->{root_keyword};
|
||||
$context->{result} = $self->{pbot}->{factoids}->handle_action($context, $context->{result});
|
||||
}
|
||||
|
||||
# if nick isn't overridden yet, check for a potential nick prefix
|
||||
if (not defined $context->{nickoverride}) {
|
||||
# if add_nick is set on the factoid, set the nick override to the caller's nick
|
||||
if (exists $context->{special} and $context->{special} ne 'code-factoid'
|
||||
and $self->{pbot}->{factoids}->{factoids}->exists($context->{channel}, $context->{trigger}, 'add_nick')
|
||||
and $self->{pbot}->{factoids}->{factoids}->get_data($context->{channel}, $context->{trigger}, 'add_nick') != 0)
|
||||
{
|
||||
@ -263,14 +342,16 @@ sub process_pipe_reader {
|
||||
$context->{no_nickoverride} = 0;
|
||||
$context->{force_nickoverride} = 1;
|
||||
} else {
|
||||
# extract nick-like thing from module result
|
||||
# extract nick-like thing from process result
|
||||
if ($context->{result} =~ s/^(\S+): //) {
|
||||
my $nick = $1;
|
||||
|
||||
if (lc $nick eq "usage") {
|
||||
# put it back on result if it's a usage message
|
||||
$context->{result} = "$nick: $context->{result}";
|
||||
} else {
|
||||
my $present = $self->{pbot}->{nicklist}->is_present($context->{channel}, $nick);
|
||||
|
||||
if ($present) {
|
||||
# nick is present in channel
|
||||
$context->{nickoverride} = $present;
|
||||
@ -281,13 +362,11 @@ sub process_pipe_reader {
|
||||
}
|
||||
}
|
||||
}
|
||||
$self->{pbot}->{interpreter}->handle_result($context, $context->{result});
|
||||
}
|
||||
|
||||
my $text = $self->{pbot}->{interpreter}
|
||||
->truncate_result($context->{channel}, $self->{pbot}->{registry}->get_value('irc', 'botnick'), 'undef', $context->{result}, $context->{result}, 0);
|
||||
$self->{pbot}->{antiflood}
|
||||
->check_flood($context->{from}, $self->{pbot}->{registry}->get_value('irc', 'botnick'), $self->{pbot}->{registry}->get_value('irc', 'username'), 'pbot', $text, 0, 0, 0);
|
||||
# send the result off to the bot to be handled
|
||||
$context->{checkflood} = 1;
|
||||
$self->{pbot}->{interpreter}->handle_result($context, $context->{result});
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -14,6 +14,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Module::Refresh;
|
||||
use File::Basename;
|
||||
|
@ -11,6 +11,7 @@ package PBot::Registerable;
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
sub new {
|
||||
my ($proto, %conf) = @_;
|
||||
|
@ -14,6 +14,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
use PBot::RegistryCommands;
|
||||
|
@ -12,6 +12,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
|
@ -12,6 +12,7 @@ package PBot::SQLiteLogger;
|
||||
|
||||
use strict; use warnings;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
|
@ -13,6 +13,7 @@ use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
sub PUSHED {
|
||||
my ($class, $mode, $fh) = @_;
|
||||
|
@ -7,6 +7,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use IO::Select;
|
||||
|
||||
@ -18,10 +19,10 @@ sub initialize {
|
||||
}
|
||||
|
||||
sub add_reader {
|
||||
my ($self, $handle, $sub) = @_;
|
||||
my ($self, $handle, $subref) = @_;
|
||||
$self->{select}->add($handle);
|
||||
$self->{readers}->{$handle} = $sub;
|
||||
$self->{buffers}->{$handle} = "";
|
||||
$self->{readers}->{$handle} = $subref;
|
||||
$self->{buffers}->{$handle} = '';
|
||||
}
|
||||
|
||||
sub remove_reader {
|
||||
@ -33,32 +34,61 @@ sub remove_reader {
|
||||
|
||||
sub do_select {
|
||||
my ($self) = @_;
|
||||
|
||||
# maximum read length
|
||||
my $length = 8192;
|
||||
|
||||
# check if any readers can read
|
||||
my @ready = $self->{select}->can_read(.1);
|
||||
|
||||
foreach my $fh (@ready) {
|
||||
# read from handle
|
||||
my $ret = sysread($fh, my $buf, $length);
|
||||
|
||||
# error reading
|
||||
if (not defined $ret) {
|
||||
$self->{pbot}->{logger}->log("Error with $fh: $!\n");
|
||||
$self->{pbot}->{logger}->log("SelectHandler: Error reading $fh: $!\n");
|
||||
$self->remove_reader($fh);
|
||||
next;
|
||||
}
|
||||
|
||||
# reader closed
|
||||
if ($ret == 0) {
|
||||
if (length $self->{buffers}->{$fh}) { $self->{readers}->{$fh}->($self->{buffers}->{$fh}); }
|
||||
# is there anything in reader's buffer?
|
||||
if (length $self->{buffers}->{$fh}) {
|
||||
# send buffer to reader subref
|
||||
$self->{readers}->{$fh}->($self->{buffers}->{$fh});
|
||||
}
|
||||
|
||||
# remove reader
|
||||
$self->remove_reader($fh);
|
||||
|
||||
# skip to next reader
|
||||
next;
|
||||
}
|
||||
|
||||
# sanity check for missing reader
|
||||
if (not exists $self->{readers}->{$fh}) {
|
||||
$self->{pbot}->{logger}->log("Error: no reader for $fh\n");
|
||||
|
||||
# skip to next reader
|
||||
next;
|
||||
}
|
||||
|
||||
# accumulate input into reader's buffer
|
||||
$self->{buffers}->{$fh} .= $buf;
|
||||
|
||||
if (not exists $self->{readers}->{$fh}) { $self->{pbot}->{logger}->log("Error: no reader for $fh\n"); }
|
||||
else {
|
||||
if ($ret < $length) {
|
||||
$self->{readers}->{$fh}->($self->{buffers}->{$fh});
|
||||
$self->{buffers}->{$fh} = "";
|
||||
}
|
||||
# if we read less than max length bytes then this is probably
|
||||
# a complete message so send it to reader now, otherwise we'll
|
||||
# continue to accumulate input into reader's buffer and then send
|
||||
# the buffer when reader closes.
|
||||
|
||||
if ($ret < $length) {
|
||||
# send reader's buffer to reader subref
|
||||
$self->{readers}->{$fh}->($self->{buffers}->{$fh});
|
||||
|
||||
# clear out reader's buffer
|
||||
$self->{buffers}->{$fh} = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,14 +7,18 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use POSIX qw(tcgetpgrp getpgrp); # to check whether process is in background or foreground
|
||||
use POSIX qw(tcgetpgrp getpgrp); # to check whether process is in background or foreground
|
||||
|
||||
use Encode;
|
||||
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
|
||||
# create implicit bot-admin account for bot
|
||||
# create stdin bot-admin account for bot
|
||||
my $user = $self->{pbot}->{users}->find_user('.*', '*!stdin@pbot');
|
||||
|
||||
if (not defined $user or not $self->{pbot}->{capabilities}->userhas($user, 'botowner')) {
|
||||
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
|
||||
$self->{pbot}->{logger}->log("Adding stdin botowner *!stdin\@pbot...\n");
|
||||
@ -23,18 +27,25 @@ sub initialize {
|
||||
$self->{pbot}->{users}->save;
|
||||
}
|
||||
|
||||
# used to check whether process is in background or foreground, for stdin reading
|
||||
if (not $self->{pbot}->{registry}->get_value('general', 'daemon')) {
|
||||
# TTY is used to check whether process is in background or foreground
|
||||
open TTY, "</dev/tty" or die $!;
|
||||
$self->{tty_fd} = fileno(TTY);
|
||||
|
||||
# add STDIN to select handler
|
||||
$self->{pbot}->{select_handler}->add_reader(\*STDIN, sub { $self->stdin_reader(@_) });
|
||||
} else {
|
||||
$self->{pbot}->{logger}->log("Starting in daemon mode.\n");
|
||||
# TODO: close STDIN, etc?
|
||||
}
|
||||
}
|
||||
|
||||
sub stdin_reader {
|
||||
my ($self, $input) = @_;
|
||||
|
||||
# decode STDIN input from utf8
|
||||
$input = decode('UTF-8', $input);
|
||||
|
||||
chomp $input;
|
||||
|
||||
# make sure we're in the foreground first
|
||||
@ -46,14 +57,17 @@ sub stdin_reader {
|
||||
|
||||
my ($from, $text);
|
||||
|
||||
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
|
||||
|
||||
if ($input =~ m/^~([^ ]+)\s+(.*)/) {
|
||||
$from = $1;
|
||||
$text = $self->{pbot}->{registry}->get_value('irc', 'botnick') . " $2";
|
||||
$text = "$botnick $2";
|
||||
} else {
|
||||
$from = 'stdin@pbot';
|
||||
$text = $self->{pbot}->{registry}->get_value('irc', 'botnick') . " $input";
|
||||
$text = "$botnick $input";
|
||||
}
|
||||
return $self->{pbot}->{interpreter}->process_line($from, $self->{pbot}->{registry}->get_value('irc', 'botnick'), "stdin", "pbot", $text);
|
||||
|
||||
return $self->{pbot}->{interpreter}->process_line($from, $botnick, "stdin", "pbot", $text);
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -21,6 +21,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::Duration qw/concise duration/;
|
||||
|
||||
|
@ -14,6 +14,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use File::Basename;
|
||||
|
||||
|
@ -12,6 +12,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
|
@ -13,6 +13,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use strict; use warnings;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use LWP::UserAgent;
|
||||
|
||||
|
@ -13,6 +13,7 @@ use parent 'PBot::Class';
|
||||
|
||||
use warnings; use strict;
|
||||
use feature 'unicode_strings';
|
||||
use utf8;
|
||||
|
||||
use Time::HiRes qw/gettimeofday/;
|
||||
use Time::Duration;
|
||||
@ -36,8 +37,10 @@ sub initialize {
|
||||
sub get_paste_site {
|
||||
my ($self) = @_;
|
||||
|
||||
# get the next paste site's subroutine reference
|
||||
my $subref = $self->{paste_sites}->[$self->{current_site}];
|
||||
|
||||
# rotate current_site
|
||||
if (++$self->{current_site} >= @{$self->{paste_sites}}) {
|
||||
$self->{current_site} = 0;
|
||||
}
|
||||
@ -54,22 +57,31 @@ sub paste {
|
||||
|
||||
%opts = (%default_opts, %opts);
|
||||
|
||||
# word-wrap text unless no_split is set
|
||||
$text =~ s/(.{120})\s/$1\n/g unless $opts{no_split};
|
||||
|
||||
# encode paste to utf8
|
||||
$text = encode('UTF-8', $text);
|
||||
|
||||
my $response;
|
||||
|
||||
for (my $tries = 3; $tries > 0; $tries--) {
|
||||
# get the next paste site
|
||||
my $paste_site = $self->get_paste_site;
|
||||
|
||||
# attempt to paste text
|
||||
$response = $paste_site->($text);
|
||||
|
||||
# exit loop if paste succeeded
|
||||
last if $response->is_success;
|
||||
}
|
||||
|
||||
# all tries failed
|
||||
if (not $response->is_success) {
|
||||
return "error pasting: " . $response->status_line;
|
||||
}
|
||||
|
||||
# success, return URL
|
||||
my $result = $response->decoded_content;
|
||||
|
||||
$result =~ s/^\s+|\s+$//g;
|
||||
|
Loading…
Reference in New Issue
Block a user