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   84303 use strict;
  1         2  
  1         22  
5 1     1   3 use warnings;
  1         1  
  1         18  
6              
7 1     1   503 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.03'; # 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(
118             ( $item->{dc} and $item->{dc}{date} ) ? $item->{dc}{date} : $item->{pubDate}
119             );
120             next if ( not $time or $time < $since );
121              
122             my $key = join( '|',
123             $url->{url},
124             $item->{title},
125             $item->{link},
126             $time,
127             );
128             next if ( $seen->{$key} );
129             $seen->{$key} = $now;
130              
131             my $msg =
132             'Feed: ' . $rss->channel('title') .
133             ' [ ' . $item->{title} . ' ]' .
134             ' (' . makeashorterlink( $item->{link} ) . ')';
135             $msg .= ' -- ' . $item->{comments} if ( $item->{comments} );
136              
137             $bot->msg( $_, $msg ) for ( ( @{ $url->{forums} } ) ? @{ $url->{forums} } : @$channels );
138              
139             last if ( ++$printed >= $max_per );
140             }
141             }
142              
143             $bot->store->set( 'seen' => $seen );
144             },
145             );
146              
147             $bot->helps( feeds =>
148             'Watch and notify on changes in RSS feeds. ' .
149             'Usage: bot feed add URL [FORUMS], bot feed list, bot feed remove URL. ' .
150             'See also: https://metacpan.org/pod/Bot::IRC::X::Feeds'
151             );
152             }
153              
154             1;
155              
156             __END__
157              
158             =pod
159              
160             =encoding UTF-8
161              
162             =head1 NAME
163              
164             Bot::IRC::X::Feeds - Bot::IRC plugin to watch and notify on changes in RSS feeds
165              
166             =head1 VERSION
167              
168             version 1.03
169              
170             =for markdown [![Build Status](https://travis-ci.org/gryphonshafer/Bot-IRC-X-Feeds.svg)](https://travis-ci.org/gryphonshafer/Bot-IRC-X-Feeds)
171             [![Coverage Status](https://coveralls.io/repos/gryphonshafer/Bot-IRC-X-Feeds/badge.png)](https://coveralls.io/r/gryphonshafer/Bot-IRC-X-Feeds)
172              
173             =head1 SYNOPSIS
174              
175             use Bot::IRC;
176              
177             Bot::IRC->new(
178             connect => { server => 'irc.perl.org' },
179             plugins => ['Feeds'],
180             vars => {
181             x-feeds => {
182             interval => 10,
183             max_per => 5,
184             fresh_intervals => 2,
185             }
186             },
187             )->run;
188              
189             =head1 DESCRIPTION
190              
191             This L<Bot::IRC> plugin adds functionality so bots can watch and notify on
192             changes in RSS feeds. You can tell the bot to start following feeds.
193              
194             bot feed add URL [FORUMS]
195              
196             You can optionally provide a "FORUMS" string, which is a list of channels the
197             bot should report on for the feed. By default, the bot reports on all channels
198             it has joined. (Requires the L<Bot::IRC::Join> plugin.) The list of channels
199             must be comma-delimited with no spaces.
200              
201             You can list the feeds the bot is following:
202              
203             bot feed list
204              
205             And you can remove feeds from being watched:
206              
207             bot feed remove URL
208              
209             You can also remove all feeds from being watched:
210              
211             bot feed remove all
212              
213             =head2 Configuration Settings
214              
215             Setting the C<x-feeds> values allows for configuration.
216              
217             Bot::IRC->new(
218             connect => { server => 'irc.perl.org' },
219             plugins => ['Feeds'],
220             vars => {
221             x-feeds => {
222             interval => 10,
223             max_per => 5,
224             fresh_intervals => 2,
225             }
226             },
227             )->run;
228              
229             The "interval" value is the time interval between calls to feeds, measured in
230             minutes.
231              
232             The "max_per" value is the number of items returned per feed per call.
233              
234             The "fresh_intervals" setting means how many intervals of time backward should
235             items be considered fresh enough to report on. For example, if you set an
236             interval of 5 minutes and a fresh_intervals of 3, then any item in a feed with
237             a publication time older than 15 will not be reported.
238              
239             The default values for all are shown in the example above.
240              
241             =head1 SEE ALSO
242              
243             You can look for additional information at:
244              
245             =over 4
246              
247             =item *
248              
249             L<Bot::IRC>
250              
251             =item *
252              
253             L<GitHub|https://github.com/gryphonshafer/Bot-IRC-X-Feeds>
254              
255             =item *
256              
257             L<CPAN|http://search.cpan.org/dist/Bot-IRC-X-Feeds>
258              
259             =item *
260              
261             L<MetaCPAN|https://metacpan.org/pod/Bot::IRC::X::Feeds>
262              
263             =item *
264              
265             L<AnnoCPAN|http://annocpan.org/dist/Bot-IRC-X-Feeds>
266              
267             =item *
268              
269             L<Travis CI|https://travis-ci.org/gryphonshafer/Bot-IRC-X-Feeds>
270              
271             =item *
272              
273             L<Coveralls|https://coveralls.io/r/gryphonshafer/Bot-IRC-X-Feeds>
274              
275             =item *
276              
277             L<CPANTS|http://cpants.cpanauthors.org/dist/Bot-IRC-X-Feeds>
278              
279             =item *
280              
281             L<CPAN Testers|http://www.cpantesters.org/distro/T/Bot-IRC-X-Feeds.html>
282              
283             =back
284              
285             =for Pod::Coverage init
286              
287             =head1 AUTHOR
288              
289             Gryphon Shafer <gryphon@cpan.org>
290              
291             =head1 COPYRIGHT AND LICENSE
292              
293             This software is copyright (c) 2016 by Gryphon Shafer.
294              
295             This is free software; you can redistribute it and/or modify it under
296             the same terms as the Perl 5 programming language system itself.
297              
298             =cut