App-KGB

 view release on metacpan or  search on metacpan

script/kgb-bot  view on Meta::CPAN

    else {
        push @info, join( ' ', map { colorize_change($_) } @$changes )
            if @$changes;
    }

    my @string = join( ' ', @info );

    my $web_string
        = defined( $data->{extra}{web_link} )
        ? colorize( web => $data->{extra}{web_link} )
        : undef;

    my $use_notices = $data->{extra}{use_irc_notices};

    # one-line notifications result in:
    #  user branch commit module changes log link
    # multi-line notifications look like:
    #  user branch commit module changes link
    #  log line 1
    #  log line 2 ...
    if ( 1 == @log and length($log[0]) <= 80 ) {
        $string[0] .= ' ' . colorize( separator => '*' ) . ' ' . $log[0];
    }
    else {
        push @string, @log;
    }

    $string[0] .= ' ' . colorize( separator => '*' ) . ' ' . $web_string
        if defined($web_string);

    @string = trim_lines(\@chanids, $repo_id, @string);

    foreach my $chanid ( @chanids ) {
        if ($KGB::simulate) {
            my $fh = IO::File->new(">> $KGB::simulate")
                or die "Error opening $KGB::simulate for writing: $!\n";
            $fh->autoflush(1);
            $fh->binmode(':utf8');
            for (@string) {
                $fh->print("$chanid $_\n");
            }
            $fh->close;
        }
        else {
            my ( $net, $chan ) = KGB->get_net_chan($chanid);
            $kernel->yield(
                irc_notify => $net, $chan, \@string,
                $use_notices ? 'notice' : 'privmsg'
            );
        }
    }
}

sub do_commit_v0 {
    my ( $kernel, $repo_id, $passwd, $rev, $paths, $log, $author )
        = @_;

    throw Error::Simple("Unknown repository '$repo_id'\n")
        unless $KGB::config->{repositories}{$repo_id};

    throw Error::Simple("Invalid password for repository $repo_id\n")
        if $KGB::config->{repositories}{$repo_id}{password}
        and $KGB::config->{repositories}{$repo_id}{password} ne $passwd;

    do_commit_msg(
        $kernel,
        $repo_id,
        {   rev_prefix => 'r',
            commit_id  => $rev,
            changes    => $paths,
            commit_log => $log,
            author     => $author
        }
    );
}

sub do_commit_v1 {
    my ($kernel, $repo_id, $checksum, $rev,
        $paths,  $log,      $author,  $branch,   $module
    ) = @_;

    # v1 is the same as v2, but has no rev_prefix parameter
    return do_commit_v2(
        $kernel, $repo_id, $checksum, 'r', $rev,
        $paths,  $log,      $author,  $branch,   $module
    );
}

sub do_commit_v2 {
    my ($kernel,     $repo_id, $checksum,
        $rev_prefix, $rev,      $paths,   $log,
        $author,     $branch,   $module,
    ) = @_;

    throw Error::Simple("Repository $repo_id is unknown\n")
        unless $KGB::config->{repositories}{$repo_id};

    # Protocol v2 always uses UTF-8
    utf8::decode($_)
        for ( $repo_id, $rev, @$paths, $log, $author, $branch, $module );
    my $message = join( "",
        $repo_id,
        $rev // (),
        @$paths,
        $log,
        ( defined($author) ? $author : () ),
        ( defined($branch) ? $branch : () ),
        ( defined($module) ? $module : () ),
        $KGB::config->{repositories}{$repo_id}{password} );
    utf8::encode($message);    # Convert to byte-sequence

    throw Error::Simple("Authentication failed for repository $repo_id\n")
        if $KGB::config->{repositories}{$repo_id}{password}
        and sha1_hex($message) ne $checksum;

    do_commit_msg(
        $kernel,
        $repo_id,
        {   rev_prefix => $rev_prefix,
            commit_id  => $rev,
            changes    => $paths,
            commit_log => $log,
            author     => $author,
            branch     => $branch,
            module     => $module
        }
    );
}

sub do_commit_v3 {
    my ( $kernel, $repo_id, $serialized, $checksum ) = @_;

    throw Error::Simple("Repository $repo_id is unknown\n")
        unless exists $KGB::config->{repositories}{$repo_id};

    my $pwd = $KGB::config->{repositories}{$repo_id}{password};
    throw Error::Simple("Authentication failed for repository $repo_id\n")
        if not defined($pwd)
        or sha1_hex( $repo_id, $serialized, $pwd ) ne $checksum;

    my $data;
    my $ok = eval { $data = Storable::thaw($serialized); 1 };

    throw Error::Simple("Invalid serialized data\n")
        unless $ok;

    do_commit_msg( $kernel, $repo_id, $data );
}

sub commit {
    my $kernel   = $_[KERNEL];
    my $response = $_[ARG0];
    my $params   = $response->soapbody();

    my $result;
    try {
        $result = do_commit( $kernel, $params );

        $response->content("OK");
        $kernel->post( SOAPServer => 'DONE', $response );
    }
    catch Error::Simple with {
        my $E = shift;
        KGB->out("$E");
        $kernel->post(
            SOAPServer => 'FAULT',
            $response, 'Client.Arguments',
            "$E",
        );
    }
    otherwise {
        my $E = shift;
        KGB->out("commit crashed: $E");
        $kernel->post(
            SOAPServer => 'FAULT',
            $response, 'Server.Code',
            'Internal Server Error'
        );
    };
}

sub do_commit {
    my ( $kernel, $params ) = @_;

    KGB->out( "commit: " . YAML::Dump($params) ) if $KGB::debug;

    throw Error::Simple("commit(params ...)\n")
        unless ref $params
        and ref $params eq "HASH"
        and $params->{Array}
        and ref $params->{Array}
        and ref $params->{Array} eq "ARRAY";

    my $proto_ver;
    if ( @{ $params->{Array} } == 6 ) {
        $proto_ver = 0;

script/kgb-bot  view on Meta::CPAN

    throw Error::Simple("Invalid protocol version ($proto_ver)\n");
}

package KGB::IRC;

use strict;
use warnings;

use App::KGB;
use Digest::MD5 qw(md5_hex);
use Monkey::Patch;
use POE;
use POE::Component::IRC::Common qw( parse_user matches_mask );
use POE::Component::IRC::Constants qw( MSG_PRI MSG_TEXT PRI_HIGH );
use Schedule::RateLimiter;
use Storable qw(dclone);

our %current = ();
our $irc_object;
our $autoresponse_limitter
    = Schedule::RateLimiter->new( iterations => 5, seconds => 30,
    block => 0 );

# Monkey patch to avoid delaying high priority commands when flood protection
# is enabled.
sub _sl_delayed {
    my $orig_func = shift;
    my $self = $_[OBJECT];
    return if !defined $self->{socket};

    while (@{ $self->{send_queue} } &&
        $self->{send_queue}[0][MSG_PRI] <= PRI_HIGH)
    {
        my $arg = (shift @{$self->{send_queue}})->[MSG_TEXT];
        warn ">>> $arg\n" if $self->{debug};
        $self->send_event(irc_raw_out => $arg) if $self->{raw};
        $self->{socket}->put($arg);
    }
    return $orig_func->(@_);
}
my $patch = Monkey::Patch::patch_package('POE::Component::IRC', 'sl_delayed',
    \&_sl_delayed);

# Handles the connection, disconnection and real-time configuration changes WRT
# IRC servers and channels
sub _irc_reconnect {
    my ( $kernel, $session ) = @_[ KERNEL, SESSION ];
    my ( @to_start, @to_stop, @to_restart );

    KGB->debug("_irc_reconnect called, re-sync network configuration.");
    foreach my $net ( keys %current ) {
        next unless ( defined( $current{$net} ) );
        my ( $new, $old )
            = ( $KGB::config->{networks}{$net}, $current{$net} );
        if ( !$new ) {
            push @to_stop, $net;
        }
        elsif ($new->{nick} ne $old->{nick}
            or $new->{ircname}  ne $old->{ircname}
            or $new->{username} ne $old->{username}
            or ( $new->{password} || "" ) ne ( $old->{password} || "" )
            or ( $new->{nickserv_password} || "" ) ne
            ( $old->{nickserv_password} || "" )
            or $new->{server} ne $old->{server}
            or $new->{port}   ne $old->{port}
            or ( $new->{use_ssl} // '' ) ne ( $old->{use_ssl} // '' )
    )
        {
            push @to_restart, $net;
        }
        else {
            # TODO: this doesn't account for secret changes
            # perhaps it needs to part and re-join a channel when
            # its secret is changed?
            my ( %newchan, %oldchan, %allchan );
            %newchan = %{ $new->{channels} };
            %oldchan = %{ $old->{channels} };
            %allchan = ( %newchan, %oldchan );
            foreach my $chan ( sort keys %allchan ) {
                if ( exists $newchan{$chan} and not exists $oldchan{$chan} ) {
                    KGB->out("Joining $chan...\n");
                    $kernel->post( "irc_$net" => join => $chan => $newchan{$chan} );
                }
                elsif ( not exists $newchan{$chan} and exists $oldchan{$chan} ) {
                    KGB->out("Parting $chan...\n");
                    $kernel->post( "irc_$net" => part => $chan );
                }
            }
            $current{$net} = dclone($new);
        }
    }
    foreach ( keys %{ $KGB::config->{networks} } ) {
        if ( !$current{$_} ) {
            push @to_start, $_;
        }
    }
    foreach my $net (@to_start) {
        my $opts = $KGB::config->{networks}{$net};
        $current{$net} = dclone($opts);

        my $irc = POE::Component::IRC::State->spawn(
            Alias      => "irc_$net",
            WhoJoiners => 0,
        );

        # No need to register, as it's done automatically now. If you register
        # twice, POE never exits
    }
    foreach ( @to_stop, @to_restart ) {
        KGB->out("Disconnecting from $_\n");
        $kernel->post( "irc_$_" => "shutdown" );
        delete $current{$_};
    }
    if (@to_restart) {
        $kernel->delay( "_irc_reconnect", 3 );
    }
}

sub irc_registered {
    my ( $kernel, $heap, $sender ) = @_[ KERNEL, HEAP, SENDER ];
    $irc_object = $_[ARG0];

    my $alias = $irc_object->session_alias();
    $alias =~ s/^irc_//;
    my $opts = $KGB::config->{networks}{$alias};

    $irc_object->plugin_add( $KGB::const{AJsvc},
        POE::Component::IRC::Plugin::AutoJoin->new(
            Channels => $opts->{channels},
            NickServ_delay => 60,
        )
    ) if ( $opts->{channels} );

    $irc_object->plugin_add( $KGB::const{NSsvc},
        POE::Component::IRC::Plugin::NickServID->new(
            Password => $opts->{nickserv_password},
        )
    ) if ( $opts->{nickserv_password} );

    $irc_object->plugin_add( $KGB::const{NRsvc},
        POE::Component::IRC::Plugin::NickReclaim->new() );

    $irc_object->plugin_add( $KGB::const{Connsvc},
        POE::Component::IRC::Plugin::Connector->new() );

    $irc_object->plugin_add( $KGB::const{BAsvc},
        POE::Component::IRC::Plugin::BotAddressed->new() );

    $irc_object->plugin_add(
        'CTCP' => POE::Component::IRC::Plugin::CTCP->new(
            version    => "KGB v$App::KGB::VERSION",
            userinfo   => "KGB v$App::KGB::VERSION",
            clientinfo => "VERSION USERINFO CLIENTINFO SOURCE",
            source     => "https://salsa.debian.org/kgb-team/kgb",
        )
    );

    $kernel->post(
        $sender => connect => {
            Server   => $opts->{server},
            Port     => $opts->{port},
            Nick     => $opts->{nick},
            Ircname  => $opts->{ircname},
            Username => $opts->{username},
            Password => $opts->{password},
            Flood    => $opts->{flood},
            UseSSL   => $opts->{use_ssl},
        }
    );
    undef;
}

sub _default {
    return 0 unless $KGB::debug;
    my ( $event, $args ) = @_[ ARG0 .. $#_ ];
    my $out = "$event ";
    foreach (@$args) {
        if ( ref($_) eq 'ARRAY' ) {
            $out .= "[" . join( ", ", @$_ ) . "] ";
        }
        elsif ( ref($_) eq 'HASH' ) {
            $out .= "{" . join( ", ", %$_ ) . "} ";
        }
        elsif ( defined $_ ) {
            $out .= "'$_' ";
        }
        else {
            $out .= "undef ";
        }
    }
    KGB->debug("$out\n");
    return 0;
}

sub irc_public {
    my ( $kernel, $heap, $who, $where, $what )
        = @_[ KERNEL, HEAP, ARG0, ARG1, ARG2 ];
    my $nick = parse_user($who);
    my $chan = $where->[0];

    $kernel->yield( irc_new_hash => $chan => $what );

    KGB->debug( $chan . ':<' . $nick . '> ' . $what . "\n" );
    undef;
}

sub get_net {
    my $obj = shift;

    ( my $net = $obj->get_heap()->session_alias() ) =~ s/^irc_//;

    return $net;
}

sub add_channel ($$$) {
    my ( $kernel, $network, $channel ) = @_;
    my $chanid = KGB->get_chanid($network, $channel);

    die "Unknown network \"$network\"."
        unless (exists $KGB::config->{networks}{$network});
    return if (exists $KGB::config->{chanidx}{$chanid});

    # secret-less
    $KGB::config->{networks}{$network}{channels}{$channel} = '';
    $KGB::config->{chanidx}{$chanid} = {

script/kgb-bot  view on Meta::CPAN

            ? 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}
        and defined $json->{method}
        and not ref( $json->{method} )
        and length $json->{method} )
    {
        json_error( $json, $resp, "[$repo_id] Request has no valid \"method\" member" );
        $_[KERNEL]->post( $_[SENDER], DONE => $resp );
        return;
    }
    unless (exists $json->{params}
        and defined $json->{params}
        and ref( $json->{params} )
        and ref( $json->{params} ) eq 'ARRAY'
        and length $json->{params} )
    {
        json_error( $json, $resp, "[$repo_id] Request has no valid \"params\" member" );
        $_[KERNEL]->post( $_[SENDER], DONE => $resp );
        return;
    }
    unless (exists $json->{id}
        and defined $json->{id}
        and not ref( $json->{id} )
        and length $json->{id} )
    {
        json_error( $json, $resp, "[$repo_id] Request has no valid \"id\" member" );
        $_[KERNEL]->post( $_[SENDER], DONE => $resp );
        return;
    }

    my $json_result;
    $ok = eval {
        $json_result = encode_json(
            {   id     => $json->{id} // 0,
                result => __PACKAGE__->handle_json_request( $_[KERNEL], $repo_id, $json ),
                error  => undef
            }
        );
        1;
    };
    my $error = $@;

    unless ($ok) {
        KGB->out($error);



( run in 0.508 second using v1.01-cache-2.11-cpan-2398b32b56e )