The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

use strict;
our $VERSION = '0.0103';
use POE qw/Component::WWW::CPANRatings::RSS/;
use utf8;
sub new {
my $package = shift;
my %args = @_;
my $self = bless {}, $package;
$self->{ +lc } = delete $args{ $_ }
for keys %args;
$self->{rate} ||= 60;
$self->{ua} ||= { timeout => 30 };
$self->{file} = 'cpan_ratings.rss.storable'
unless defined $self->{file};
$self->{format}
||= 'rating: {:dist:} - {:rating:} - by {:creator:} [ {:link:} ]';
$self->{channels} ||= [];
$self->{response_event} ||= 'irc_cpanratings';
$self->{auto} = 1
unless defined $self->{auto};
$self->{utf} = 1
unless defined $self->{utf};
$self->{max_ratings} = 5
unless defined $self->{max_ratings};
return $self;
}
sub PCI_register {
my ($self, $irc) = splice @_, 0, 2;
$self->{irc} = $irc;
$self->{poco} = POE::Component::WWW::CPANRatings::RSS->spawn(
debug => $self->{debug},
ua => $self->{ua},
);
$self->{SESSION_ID} = POE::Session->create(
object_states => [
$self => [qw(_start _shutdown ratings)],
],
)->ID();
return 1;
}
sub PCI_unregister {
my ($self, $irc) = splice @_, 0, 2;
$poe_kernel->call( $self->{SESSION_ID} => '_shutdown' );
delete $self->{irc};
return 1;
}
sub _start {
my ($kernel, $self) = @_[KERNEL, OBJECT];
$self->{SESSION_ID} = $_[SESSION]->ID();
$kernel->refcount_increment( $self->{SESSION_ID}, __PACKAGE__ );
$self->{poco}->fetch( {
event => 'ratings',
repeat => $self->{rate},
unique => 1,
file => $self->{file},
}
);
return;
}
sub ratings {
my ( $self, $in_ref ) = @_[ OBJECT, ARG0 ];
if ( $in_ref->{error} ) {
$self->{debug}
and warn "Got ratings error: $in_ref->{error}\n";
return;
}
my @ratings = @{ $in_ref->{ratings} || [] };
for my $review ( splice @ratings, 0, $self->{max_ratings} ) {
my $text = $self->{format};
my $rating = $review->{rating};
if ( $self->{utf} and $rating ne 'N/A' ) {
$rating = $self->_make_utf_rating( $rating );
}
elsif ( $rating ne 'N/A' ) {
$rating = "$rating/5";
}
$text =~ s/{:dist:}/$review->{dist}/g;
$text =~ s/{:rating:}/$rating/g;
$text =~ s/{:creator:}/$review->{creator}/g;
$text =~ s/{:link:}/$review->{link}/g;
if ( $self->{auto} ) {
$self->{irc}->yield( ctcp => $_ => 'ACTION ' . $text )
for @{ $self->{channels} };
}
}
my %event_output = %$in_ref;
delete $event_output{unique};
$event_output{rate} = delete $event_output{repeat};
$self->{irc}->_send_event( $self->{response_event} => \%event_output );
}
sub _shutdown {
my ($kernel, $self) = @_[KERNEL, OBJECT];
$self->{poco}->shutdown;
$kernel->alarm_remove_all();
$kernel->refcount_decrement( $self->{SESSION_ID}, __PACKAGE__ );
return;
}
sub _make_utf_rating {
my ( $self, $rating ) = @_;
my $out = '●' x int $rating;
$out .= '◐'
if $rating - int $rating;
$out .= '○' x (5 - length $out);
return $out;
}
1;
__END__
=head1 NAME
POE::Component::IRC::Plugin::WWW::CPANRatings::RSS - announce CPAN ratings on IRC from RSS feed on http://cpanratings.perl.org/
=head1 SYNOPSIS
#!/usr/bin/env perl
use strict;
use warnings;
use POE qw(Component::IRC Component::IRC::Plugin::WWW::CPANRatings::RSS);
my $irc = POE::Component::IRC->spawn(
nick => 'CPANRatings',
server => 'irc.freenode.net',
port => 6667,
ircname => 'CPAN Ratings Bot',
);
POE::Session->create(
package_states => [
main => [ qw(_start irc_001 _default irc_cpanratings) ],
],
);
$poe_kernel->run;
sub irc_cpanratings {
my $in_ref = $_[ARG0];
use Data::Dumper;
print Dumper $in_ref;
}
sub _start {
$irc->yield( register => 'all' );
$irc->plugin_add(
'cpan_ratings' =>
POE::Component::IRC::Plugin::WWW::CPANRatings::RSS->new(
channels => [ '#zofbot' ],
)
);
$irc->yield( connect => {} );
}
sub irc_001 {
$irc->yield( join => '#zofbot' );
}
* CPANRatings rating: String-String - ●○○○○ - by BKB [ http://cpanratings.perl.org/#4476 ]
* CPANRatings rating: IWL - ●○○○○ - by BKB [ http://cpanratings.perl.org/#4474 ]
* CPANRatings rating: String - ●○○○○ - by BKB [ http://cpanratings.perl.org/#4472 ]
* CPANRatings rating: String-Buffer - ●○○○○ - by BKB [ http://cpanratings.perl.org/#4470 ]
* CPANRatings rating: String-Strip - ●○○○○ - by BKB [ http://cpanratings.perl.org/#4468 ]
* CPANRatings rating: Acme-Monta - N/A - by BKB [ http://cpanratings.perl.org/#4466 ]
=head1 DESCRIPTION
The module is L<POE::Component::IRC> plugin which uses
L<POE::Component::IRC::Plugin> for its base, thus can be loaded with
L<plugin_add()> method. The module provides means to announce new
reviews posted to L<http://cpanratings.perl.org/>
=head1 CONSTRUCTOR
=head2 C<new>
# plain
$irc->plugin_add(
'cpan_ratings' =>
POE::Component::IRC::Plugin::WWW::CPANRatings::RSS->new(
channels => [ '#zofbot' ],
)
);
# juicy
$irc->plugin_add(
'cpan_ratings' =>
POE::Component::IRC::Plugin::WWW::CPANRatings::RSS->new(
channels => [ '#zofbot' ],
rate => 60;
ua => { timeout => 30 },
file => 'cpan_ratings.rss.storable',
format => 'rating: {:dist:} - {:rating:} - by {:creator:} [ {:link:} ]',
max_ratings => 10,
response_event => 'irc_cpanratings';
auto => 1,
utf => 1,
)
);
Constructs and returns a POE::Component::IRC::Plugin::WWW::CPANRatings::RSS
object suitable to be fed to L<POE::Component::IRC>'s C<plugin_add()>
method. All of which are optional. You can change all of the arguments dynamically by
accessing them as keys in a hashref of the plugin object: C<<
$plug->{rate} = 1000; >> Possible arguments are as follows
=head3 C<channels>
->new( channels => [ '#zofbot' ], );
B<Semi-mandatory>, not specifying this argument will render the plugin
useless when C<auto> option is turned on, however you can still listen
to the events emited by the plugin. Takes an arrayref as a value which
must contain the channels where the plugin will announce new ratings.
=head3 C<rate>
->new( rate => 360, );
B<Optional>. Takes a positive integer as a value which specifies the
interval of C<rate> seconds between the checks for new ratings.
B<Defaults to:> C<60>
=head3 C<ua>
->new( ua => { timeout => 30 } );
B<Optional>. Takes a hashref as a value. That hashref will be directly
dereferenced into L<LWP::UserAgent> constructor. See L<LWP::UserAgent>'s
documentation for possible keys/values. B<Defaults to:>
C<< { timeout => 30 } >>
=head3 C<file>
->new( file => 'cpan_ratings.rss.storable' );
B<Optional>. Plugin stores already reported reviews/ratings into a file.
Using the C<file> argument, which takes a scalar as a value, you can
specify the file name for storage. B<Defaults to:>
C<cpan_ratings.rss.storable>
=head3 C<format>
->new( format => 'rating: {:dist:} - {:rating:} - by {:creator:} [ {:link:} ]', );
B<Optional>. When C<auto> argument (see below) is set to a true value
the plugin will announce new reviews into the C<channels>. The
C<format> argument, which takes a string as a value, specifies the
format of the output. Currently all the announcing is done via C</me>,
let me know if you want that configurable. Special character sequences
in the C<format> string will be replaced with respective data bits in the
following fashion:
{:dist:} - name of the distribution
{:rating:} - either the number or the stars (see C<utf>) rating
{:creator:} - name of the creator of the review
{:link:} - link to the review
The special sequences can be used any number of times if you so desire.
The B<format> argument B<defaults to:> <'rating: {:dist:} - {:rating:} - by {:creator:} [ {:link:} ]'>
=head3 C<max_ratings>
->new( max_ratings => 5 );
B<Optional>. The C<max_ratings> takes a positive integer as a value
and specifies the maximum number of ratings/reviews to report at a time.
Anything over that limit won't be reported at all; considering those
reviews don't pop up like mushrooms this shouldn't be a problem.
B<Defaults to:> C<5>
=head3 C<response_event>
->new( response_event => 'irc_cpanratings' );
B<Optional>. During its operation, the plugin will emit an event
I<every time the data is fetched>. The C<response_event> specifies the
name of the event to emit. B<Defaults to:> C<irc_cpanratings>
=head3 C<auto>
->new( auto => 1 );
B<Optional>. Takes either true or false values. When set to a true value
plugin will auto-announce new reviews into all the channels specified
in C<channels> argument. B<Defaults to:> C<1>
=head3 C<utf>
->new( utf => 1 );
B<Optional>. Takes either true or false values. When set to a true value
will use UTF-8 circles to represent the given rating (that is
what will replace the C<{:rating:}> sequence in the C<format> argument).
When set to a false value will use simple numbers. B<Defaults to:> C<1>
=head1 EMITED EVENTS
$VAR1 = {
'rate' => 60,
'ratings' => [
{
'comment' => 'One of the most useful Acme modules ever! Simply fantastic to show off without losing more than 5 seconds, a real masterpiece of meta-art. I\'d suggest ...',
'creator' => 'Flavio Poletti',
'dist' => 'Acme-EyeDrops',
'rating' => '5'
},
],
'file' => 'cpan_ratings.rss.storable'
};
The event handler setup to handle the C<response_event>, which defaults
to C<irc_cpanratings>, will receive input on I<every fetch>, thus
every C<rate> (see CONSTRUCTOR) seconds. The input will come in a form
of a hashref in C<$_[ARG0]>. The keys/values of that hashref are as follows.
=head2 C<ratings>
'ratings' => [
{
'comment' => 'One of the most useful Acme modules ever! Simply fantastic to show off without losing more than 5 seconds, a real masterpiece of meta-art. I\'d suggest ...',
'creator' => 'Flavio Poletti',
'dist' => 'Acme-EyeDrops',
'rating' => '5'
},
],
The C<ratings> key will contain a (possibly empty) arrayref of hashrefs
where each hashrefs represents a new
review. See C<WWW::CPANRatings::RSS>'s C<fetch()> method of explaination
of each of the keys/values of those hashrefs.
=head3 C<error>
'error' => 'Network error: 500 Timeout',
If an error occured the C<error> key will be present and its value,
which will be a scalar, will contain the description of the error.
=head3 C<rate>
'rate' => 60
The C<rate> key will contain the rate (in seconds) at which the plugin
fetches new data.
=head3 C<file>
'file' => 'cpan_ratings.rss.storable'
The C<file> key will contain the filename of the file where the plugin
stores already reported reviews.
=head1 EXAMPLES
The C<examples/> directory of this distribution contains C<ratings_bot.pl>
which is a fully working CPAN Ratings announcing IRC bot.
=head1 AUTHOR
Zoffix Znet, C<< <zoffix at cpan.org> >>
=head1 BUGS
Please report any bugs or feature requests to C<bug-poe-component-irc-plugin-www-cpanratings-rss at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=POE-Component-IRC-Plugin-WWW-CPANRatings-RSS>. I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc POE::Component::IRC::Plugin::WWW::CPANRatings::RSS
You can also look for information at:
=over 4
=item * RT: CPAN's request tracker
=item * AnnoCPAN: Annotated CPAN documentation
=item * CPAN Ratings
=item * Search CPAN
=back
=head1 COPYRIGHT & LICENSE
Copyright 2008 Zoffix Znet, all rights reserved.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut