App-KGB
view release on metacpan or search on metacpan
script/kgb-bot view on Meta::CPAN
=item --simulate I<file>
Do not connect to IRC. Instead, output each notification line to the given
I<file>, like:
#chan repo user branch revision module changed-paths
#chan repo commit message line 1
#chan repo commit message line 2
There are no colour codes in the output, unless B<--simulate-color> is also
given.
=item --simulate-color
Include color codes in the file used by B<--simulate>.
=item --debug
Log additional debugging information
=back
=cut
package KGB;
use strict;
use warnings;
use utf8;
use open ':encoding(utf8)';
use App::KGB::Painter;
use Net::IP;
use Time::Piece qw(localtime);
use Cwd;
our $config;
our ( $config_file, $config_dir, $foreground, $debug );
our %const = (
SOAPsvc => "SOAPServer",
BAsvc => "BotAddressed",
Connsvc => "Connecter",
NSsvc => "NickServID",
NRsvc => "NickReclaim",
AJsvc => "AutoJoin",
);
our %supported_protos = (
"0" => 1,
"1" => 1,
"2" => 1,
"3" => 1,
);
our $progname;
our $restart = 0;
our $shuttingdown = 0;
our $painter;
our $painter_dummy = App::KGB::Painter->new( { simulate => 1 } );
my %stats;
my $run_time_since = time;
# for JSON::XS, used for debugging
sub Net::IP::TO_JSON {
shift->short;
}
sub save_progname () {
$progname = Cwd::realpath($0);
}
sub polygen_available () {
unless ( eval { require IPC::Run } ) {
KGB->debug("error loading IPC::Run\n");
KGB->debug($@);
return undef;
}
unless ( eval { require File::Which } ) {
KGB->debug("error loading File::Which\n");
KGB->debug($@);
return undef;
}
my $oldpath = $ENV{PATH};
$ENV{PATH}='/usr/bin/:/usr/games';
my $polygen;
unless ( $polygen = File::Which::which('polygen') ) {
KGB->debug("missing polygen binary\n");
}
$ENV{PATH} = $oldpath;
return $polygen;
}
sub merge_conf_hash($$);
sub merge_conf_hash($$) {
my ( $dst, $src ) = @_;
while ( my ($k, $v) = each %$src ) {
if ( ref($v) ) {
if ( exists $dst->{$k} ) {
die
"Error merging key '$k': source is a reference, but destination is scalar\n"
unless ref( $dst->{$k} );
ref( $dst->{$k} ) eq ref($v)
or die
"Error merging key '$k': reference type mismatch\n";
if ( ref($v) eq 'ARRAY' ) {
push @{ $dst->{$k} }, @$v;
}
elsif ( ref($v) eq 'HASH' ) {
merge_conf_hash( $dst->{$k}, $v );
}
else {
die "Error merging key '$k': unknown reference type\n";
}
}
else {
$dst->{$k} = $v;
}
}
script/kgb-bot view on Meta::CPAN
my %chanidx;
foreach ( @{ $conf->{channels} } ) {
$_->{repositories} //= [];
die "Missing channel name at channel\n" unless ( $_->{name} );
die "Invalid network at channel " . $_->{name} . "\n"
unless ( $_->{network} and $conf->{networks}{ $_->{network} } );
$conf->{networks}{ $_->{network} }{channels}{$_->{name}} = $_->{secret};
my $chanid = KGB->get_chanid( $_->{network}, $_->{name} );
die "Invalid repos key at channel $chanid\n"
unless $_->{broadcast}
or ( ref $_->{repos} and ref $_->{repos} eq "ARRAY" );
if ( $_->{broadcast} ) {
push @{ $conf->{broadcast_channels} }, $chanid;
KGB->out("Repository list ignored for broadcast channel $chanid\n")
if @{ $_->{repositories} };
}
else {
KGB->out("Channel $chanid doesn't listen on any repository\n")
unless @{ $_->{repos} };
foreach my $repo ( @{ $_->{repos} } ) {
die "Invalid repository $repo at channel $chanid\n"
unless ( $conf->{repositories}{$repo} );
push @{ $conf->{repositories}{$repo}{chanids} }, $chanid;
}
}
$_->{chanid} = $chanid;
$chanidx{$chanid} = $_;
}
$conf->{chanidx} = \%chanidx;
$conf->{colors} ||= {};
$conf->{colors}{revision} //= '';
$conf->{colors}{path} //= 'teal';
$conf->{colors}{author} //= 'purple';
$conf->{colors}{branch} //= 'brown';
$conf->{colors}{module} //= 'green';
$conf->{colors}{web} //= 'silver';
$conf->{colors}{separator} //= '';
$conf->{colors}{addition} //= 'green';
$conf->{colors}{modification} //= 'teal';
$conf->{colors}{deletion} //= 'bold red';
$conf->{colors}{replacement} //= 'brown';
$conf->{colors}{prop_change} //= 'underline';
$conf->{colors}{success} //= 'reverse green';
$conf->{colors}{failure} //= 'reverse red';
$KGB::debug = $conf->{debug} if exists $conf->{debug};
$conf->{pid_dir}
= Cwd::realpath( $conf->{pid_dir} // '/var/run/kgb-bot' );
if ( exists $conf->{webhook}
and exists $conf->{webhook}{allowed_networks} )
{
$_ = Net::IP->new($_) for @{ $conf->{webhook}{allowed_networks} };
}
KGB->debug( JSON::XS->new->convert_blessed(1)->encode($conf) );
return $conf;
}
sub load_conf($) {
my $file = shift;
my $conf = read_conf($file);
# Save globals
$config_file = Cwd::realpath($file);
$config = $conf;
return $conf;
}
sub reload_conf() {
my $new_conf = eval { KGB::read_conf($config_file) };
if ($@) {
KGB->out("Error in configuration file: $@");
return -1;
}
if ( $new_conf->{soap}{service_name} ne $config->{soap}{service_name}
or $new_conf->{soap}{server_port} ne $config->{soap}{server_port}
or $new_conf->{soap}{server_addr} ne $config->{soap}{server_addr} )
{
KGB->out("Cannot reload configuration file, restarting\n");
return -2; # need restart
}
$painter =
App::KGB::Painter->new( { item_colors => $new_conf->{colors} } );
KGB->out("Configuration file reloaded\n");
$config = $new_conf;
return 0;
}
sub out {
shift;
print $KGB::out localtime->strftime('%Y.%m.%d %H:%M:%S').': ', @_, ( $_[-1] =~ /\n$/s ) ? () : "\n";
}
sub debug {
return unless $KGB::debug;
my $self = shift;
my $first_line = shift;
$first_line = (caller)[2] . ': ' . $first_line;
$self->out( $first_line, @_ );
}
sub open_log {
if ( my $f = $KGB::config->{log_file} ) {
open( STDOUT, ">>", $f )
or die "Error opening log $f: $!\n";
open( STDERR, ">>", $f )
or die "Error opening log $f: $!\n";
}
else {
script/kgb-bot view on Meta::CPAN
return undef;
}
KGB->count('notifications');
my $alias = "irc_$net";
$kernel->post( $alias => $method => $chan => $_ ) foreach (@$str);
if ( $KGB::debug ) {
KGB->out("$net/$chan > $_\n") foreach (@$str);
}
}
sub reply {
my ( $kernel, $net, $chan, $nick, $msg ) = @_;
# put some hidden character to avoid addressing anyone
my $safety = $KGB::painter->color_codes->{normal};
return $chan
? $kernel->post( "irc_$net" => privmsg => $chan => "$safety$nick: $msg" )
: $kernel->post( "irc_$net" => privmsg => $nick => "$safety$msg" );
}
sub irc_command {
my ( $kernel, $heap, $command, $who, $chan, $net )
= @_[ KERNEL, HEAP, ARG0 .. ARG3 ];
my $nick = parse_user($who);
return reply( $kernel, $net, $chan, $nick, "You are not my master" )
unless grep { matches_mask( $_, $who ) } @{ $KGB::config->{admins} };
if ( $command eq 'version' ) {
return reply( $kernel, $net, $chan, $nick,
"Tried /CTCP "
. $KGB::config->{networks}{$net}{nick}
. " VERSION?" );
}
elsif ( $command eq 'stats' ) {
my $stats = KGB->get_stats;
my $run_time = human_duration( KGB->get_run_seconds );
my $msg = "after $run_time: ";
$msg .=
%$stats
? join( ', ', map( "$_:$stats->{$_}", sort keys %$stats ) )
: 'no stats yet';
return reply( $kernel, $net, $chan, $nick, $msg);
}
elsif ( $command eq 'statsreset' ) {
KGB->reset_stats;
return reply( $kernel, $net, $chan, $nick, 'Done' );
}
else {
return reply( $kernel, $net, $chan, $nick,
"command '$command' is not known to me" );
}
}
package KGB::JSON;
use strict;
use warnings;
use JSON::XS;
use POE;
use Digest::SHA qw(sha1_hex);
sub json_error {
my ( $json, $resp, $error ) = @_;
KGB->out($error);
$resp->code(200);
$resp->message('OK');
$resp->content(
encode_json(
{ id => $json->{id} // 0, error => $error, result => undef }
)
);
}
sub http_error {
my ( $resp, $error, $code ) = @_;
KGB->out($error);
$resp->code($code // 400);
$resp->message($error);
$resp->content('');
}
sub json_request {
my ( $req, $resp, $path ) = @_[ ARG0, ARG1, ARG2 ];
my ($repo_id, $auth);
unless (defined( $repo_id = $req->header('X-KGB-Project') )
and defined( $auth = $req->header('X-KGB-Auth') ) )
{
http_error( $resp,
'Invalid or missing X-KGB-Project or X-KGB-Auth headers' );
$_[KERNEL]->post( $_[SENDER], DONE => $resp );
return;
}
unless ( exists $KGB::config->{repositories}{$repo_id} ) {
http_error( $resp, "Unknown project ($repo_id)" );
$_[KERNEL]->post( $_[SENDER], DONE => $resp );
return;
}
my $check = sha1_hex( $KGB::config->{repositories}{$repo_id}{password},
$repo_id, $req->content );
unless ( $check eq $auth ) {
http_error( $resp, "[$repo_id] Authentication failed", 401 );
$_[KERNEL]->post( $_[SENDER], DONE => $resp );
return;
}
my $json;
my $ok = eval { $json = decode_json( $req->content ); 1; };
unless ($ok) {
http_error( $resp, "[$repo_id] Error decoding JSON request" );
$_[KERNEL]->post( $_[SENDER], DONE => $resp );
return;
}
unless (exists $json->{method}
( run in 1.451 second using v1.01-cache-2.11-cpan-13bb782fe5a )