File Coverage

blib/lib/Bot/IRC/X/Feeds.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Bot::IRC::X::Feeds;
2             # ABSTRACT: Bot::IRC plugin to watch and notify on changes in RSS feeds
3              
4 1     1   106920 use strict;
  1         3  
  1         41  
5 1     1   6 use warnings;
  1         2  
  1         37  
6              
7 1     1   621 use XML::RSS;
  0            
  0            
8             use LWP::UserAgent;
9             use LWP::Protocol::https;
10             use Date::Parse 'str2time';
11             use WWW::Shorten qw( TinyURL makeashorterlink );
12              
13             our $VERSION = '1.02'; # VERSION
14              
15             sub init {
16             my ($bot) = @_;
17             $bot->load('Store');
18             $bot->load('Join');
19              
20             $bot->hook(
21             {
22             to_me => 1,
23             text => qr/
24             ^feed[s]?
25             \s+(?<action>add|list|remove)
26             (?:
27             \s+(?<url>\S+)
28             (?:
29             \s+(?<forums>\S+)
30             )?
31             )?
32             /x,
33             },
34             sub {
35             my ( $bot, $in, $m ) = @_;
36             my @urls = @{ $bot->store->get('urls') || [] };
37              
38             if ( $m->{action} eq 'add' ) {
39             my $url = lc( $m->{url} );
40             if ( grep { $_->{url} eq $url } @urls ) {
41             $bot->reply_to(q{I'm already tracking that feed.});
42             }
43             else {
44             push( @urls, {
45             url => $url,
46             forums => [ split( ',', lc $m->{forums} ) ],
47             } );
48             $bot->reply_to(q{OK. I'll report changes from the feed you provided.});
49             $bot->store->set( 'urls' => \@urls );
50             }
51             }
52             elsif ( $m->{action} eq 'list' ) {
53             if (@urls) {
54             $bot->reply($_) for ( map {
55             ( @{ $_->{forums} } )
56             ? $_->{url} . ' (' . join( ', ', @{ $_->{forums} } ) . ')'
57             : $_->{url}
58             } @urls );
59             }
60             else {
61             $bot->reply_to(q{I'm currently not tracking any feeds.});
62             }
63             }
64             elsif ( $m->{action} eq 'remove' ) {
65             my $url = lc( $m->{url} );
66             if ( $url eq 'all' ) {
67             $bot->reply_to(q{OK. I'll stop reporting on all the feeds I've been tracking.});
68             $bot->store->set( 'urls' => [] );
69             }
70             elsif ( grep { $_->{url} eq $url } @urls ) {
71             $bot->reply_to(q{OK. I'll stop reporting on the feed you provided.});
72             $bot->store->set( 'urls' => [ grep { $_->{url} ne $url } @urls ] );
73             }
74             else {
75             $bot->reply_to(qq{I wasn't able to find the feed you specified. ($url)});
76             }
77             }
78              
79             return 1;
80             },
81             );
82              
83             my $ua = LWP::UserAgent->new;
84             my $rss = XML::RSS->new;
85             my $interval = ( $bot->vars->{interval} || 10 ) * 60;
86             my $max_per = $bot->vars->{max_per} || 5;
87             my $fresh_intervals = $bot->vars->{fresh_intervals} || 2;
88              
89             $bot->tick(
90             $interval,
91             sub {
92             my ($bot) = @_;
93              
94             my $seen = $bot->store->get('seen') || {};
95             my $now = time;
96             my $since = $now - $interval * $fresh_intervals;
97             my $channels = $bot->channels;
98              
99             for ( keys %$seen ) {
100             delete $seen->{$_} if ( $seen->{$_} < $since );
101             }
102              
103             for my $url ( @{ $bot->store->get('urls') || [] } ) {
104             my $res = $ua->get( $url->{url} );
105             next unless ( $res->is_success );
106              
107             eval {
108             $rss->parse( $res->decoded_content );
109             };
110             if ($@) {
111             warn $@;
112             next;
113             }
114              
115             my $printed = 0;
116             for my $item ( @{ $rss->{items} } ) {
117             my $time = str2time( $item->{pubDate} );
118             next if ( $time < $since );
119              
120             my $key = join( '|',
121             $url->{url},
122             $item->{title},
123             $item->{link},
124             $time,
125             );
126             next if ( $seen->{$key} );
127             $seen->{$key} = $now;
128              
129             my $msg =
130             'Feed: ' . $rss->channel('title') .
131             ' [ ' . $item->{title} . ' ]' .
132             ' (' . makeashorterlink( $item->{link} ) . ')';
133             $msg .= ' -- ' . $item->{comments} if ( $item->{comments} );
134              
135             $bot->msg( $_, $msg ) for ( ( @{ $url->{forums} } ) ? @{ $url->{forums} } : @$channels );
136              
137             last if ( ++$printed >= $max_per );
138             }
139             }
140              
141             $bot->store->set( 'seen' => $seen );
142             },
143             );
144              
145             $bot->helps( feeds =>
146             'Watch and notify on changes in RSS feeds. ' .
147             'Usage: bot feed add URL [FORUMS], bot feed list, bot feed remove URL. ' .
148             'See also: https://metacpan.org/pod/Bot::IRC::X::Feeds'
149             );
150             }
151              
152             1;
153              
154             __END__
155              
156             =pod
157              
158             =encoding UTF-8
159              
160             =head1 NAME
161              
162             Bot::IRC::X::Feeds - Bot::IRC plugin to watch and notify on changes in RSS feeds
163              
164             =head1 VERSION
165              
166             version 1.02
167              
168             =for markdown [![Build Status](https://travis-ci.org/gryphonshafer/Bot-IRC-X-Feeds.svg)](https://travis-ci.org/gryphonshafer/Bot-IRC-X-Feeds)
169             [![Coverage Status](https://coveralls.io/repos/gryphonshafer/Bot-IRC-X-Feeds/badge.png)](https://coveralls.io/r/gryphonshafer/Bot-IRC-X-Feeds)
170              
171             =head1 SYNOPSIS
172              
173             use Bot::IRC;
174              
175             Bot::IRC->new(
176             connect => { server => 'irc.perl.org' },
177             plugins => ['Feeds'],
178             vars => {
179             x-feeds => {
180             interval => 10,
181             max_per => 5,
182             fresh_intervals => 2,
183             }
184             },
185             )->run;
186              
187             =head1 DESCRIPTION
188              
189             This L<Bot::IRC> plugin adds functionality so bots can watch and notify on
190             changes in RSS feeds. You can tell the bot to start following feeds.
191              
192             bot feed add URL [FORUMS]
193              
194             You can optionally provide a "FORUMS" string, which is a list of channels the
195             bot should report on for the feed. By default, the bot reports on all channels
196             it has joined. (Requires the L<Bot::IRC::Join> plugin.) The list of channels
197             must be comma-delimited with no spaces.
198              
199             You can list the feeds the bot is following:
200              
201             bot feed list
202              
203             And you can remove feeds from being watched:
204              
205             bot feed remove URL
206              
207             You can also remove all feeds from being watched:
208              
209             bot feed remove all
210              
211             =head2 Configuration Settings
212              
213             Setting the C<x-feeds> values allows for configuration.
214              
215             Bot::IRC->new(
216             connect => { server => 'irc.perl.org' },
217             plugins => ['Feeds'],
218             vars => {
219             x-feeds => {
220             interval => 10,
221             max_per => 5,
222             fresh_intervals => 2,
223             }
224             },
225             )->run;
226              
227             The "interval" value is the time interval between calls to feeds, measured in
228             minutes.
229              
230             The "max_per" value is the number of items returned per feed per call.
231              
232             The "fresh_intervals" setting means how many intervals of time backward should
233             items be considered fresh enough to report on. For example, if you set an
234             interval of 5 minutes and a fresh_intervals of 3, then any item in a feed with
235             a publication time older than 15 will not be reported.
236              
237             The default values for all are shown in the example above.
238              
239             =head1 SEE ALSO
240              
241             You can look for additional information at:
242              
243             =over 4
244              
245             =item *
246              
247             L<Bot::IRC>
248              
249             =item *
250              
251             L<GitHub|https://github.com/gryphonshafer/Bot-IRC-X-Feeds>
252              
253             =item *
254              
255             L<CPAN|http://search.cpan.org/dist/Bot-IRC-X-Feeds>
256              
257             =item *
258              
259             L<MetaCPAN|https://metacpan.org/pod/Bot::IRC::X::Feeds>
260              
261             =item *
262              
263             L<AnnoCPAN|http://annocpan.org/dist/Bot-IRC-X-Feeds>
264              
265             =item *
266              
267             L<Travis CI|https://travis-ci.org/gryphonshafer/Bot-IRC-X-Feeds>
268              
269             =item *
270              
271             L<Coveralls|https://coveralls.io/r/gryphonshafer/Bot-IRC-X-Feeds>
272              
273             =item *
274              
275             L<CPANTS|http://cpants.cpanauthors.org/dist/Bot-IRC-X-Feeds>
276              
277             =item *
278              
279             L<CPAN Testers|http://www.cpantesters.org/distro/T/Bot-IRC-X-Feeds.html>
280              
281             =back
282              
283             =for Pod::Coverage init
284              
285             =head1 AUTHOR
286              
287             Gryphon Shafer <gryphon@cpan.org>
288              
289             =head1 COPYRIGHT AND LICENSE
290              
291             This software is copyright (c) 2016 by Gryphon Shafer.
292              
293             This is free software; you can redistribute it and/or modify it under
294             the same terms as the Perl 5 programming language system itself.
295              
296             =cut