File Coverage

blib/lib/Bot/WootOff.pm
Criterion Covered Total %
statement 55 124 44.3
branch 11 42 26.1
condition n/a
subroutine 14 23 60.8
pod 2 9 22.2
total 82 198 41.4


line stmt bran cond sub pod time code
1             ###########################################
2             ###########################################
3             package Bot::WootOff;
4             ###########################################
5             ###########################################
6 3     3   67530 use strict;
  3         6  
  3         113  
7 3     3   14 use warnings;
  3         6  
  3         86  
8 3     3   2985 use HTTP::Request::Common qw(GET);
  3         90692  
  3         258  
9 3     3   3934 use POE qw(Component::Client::HTTP);
  3         173909  
  3         18  
10 3     3   1031207 use Log::Log4perl qw(:easy);
  3         226234  
  3         25  
11              
12             our $VERSION = "0.09";
13              
14             ###########################################
15             sub new {
16             ###########################################
17 4     4 1 3693 my($class, %options) = @_;
18              
19 4         232 my $self = {
20             irc_server => "irc.freenode.net",
21             irc_channel => "#wootoff" . sprintf("%04d", int(rand(1000))),
22             irc_nick => "wootbot",
23             http_alias => "wootoff-ua",
24             http_timeout => 60,
25             http_max_retries => 30,
26             poll_interval => 30,
27             Alias => "wootoff-bot",
28             spawn => 1,
29             last_msg => undef,
30             %options,
31             };
32              
33 4         13 bless $self, $class;
34              
35 4 50       49 $self->{http_agent} = $self->agent() unless defined $self->{http_agent};
36 4 50       27 $self->{http_url} = $self->woot_url() unless defined $self->{http_url};
37              
38             # Start it up automatically.
39 4 50       19 $self->spawn() if $self->{spawn};
40              
41 4         16 return $self;
42             }
43              
44             ###########################################
45             sub woot_url {
46             ###########################################
47 4     4 0 7 my($self) = @_;
48              
49 4         13 return "http://www.woot.com";
50             }
51              
52             ###########################################
53             sub agent {
54             ###########################################
55 4     4 0 9 my($self) = @_;
56              
57 4         18 return(__PACKAGE__ . "/" . $VERSION),
58             }
59              
60             ###########################################
61             sub spawn {
62             ###########################################
63 0     0 0 0 my($self) = @_;
64              
65 0         0 $self->{bot} = Bot::WootOff::Glue->new(
66             server => $self->{irc_server},
67             channels => [ $self->{irc_channel} ],
68             nick => $self->{irc_nick},
69             );
70              
71 0         0 POE::Component::Client::HTTP->spawn(
72             Agent => $self->{http_agent},
73             Alias => $self->{http_alias},
74             Timeout => $self->{http_timeout},
75             );
76              
77 0         0 my $request = GET( $self->{http_url} );
78              
79 0         0 our $last_item = "";
80              
81             POE::Session->create(
82             inline_states => {
83              
84             _start => sub {
85             # wait at startup for things to settle down
86 0     0   0 $poe_kernel->delay('http_start', 10);
87             },
88              
89             http_start => sub {
90 0     0   0 DEBUG "Requesting $self->{http_url}";
91 0         0 POE::Kernel->post( $self->{http_alias},
92             'request', 'http_ready', $request);
93             },
94              
95             http_ready => sub {
96 0     0   0 my $retries = 0;
97              
98 0         0 my $resp= $_[ARG1]->[0];
99 0 0       0 if($resp->is_success()) {
100 0         0 my $text = $resp->content();
101              
102 0         0 my($item, $price) = $self->html_scrape($text);
103              
104 0 0       0 if(! defined $item) {
105 0         0 my $save_file = "/tmp/woot.html";
106              
107 0 0       0 if(open FILE, ">$save_file") {
108 0         0 print FILE $text;
109 0         0 close FILE;
110             } else {
111 0         0 LOGWARN "Can't open $save_file ($!)";
112             }
113              
114 0 0       0 if($self->{http_retries_performed} ==
115             $self->{http_max_retries}) {
116 0         0 LOGDIE "Ouch, woot.com changed their HTML, and our ",
117             "scraper failed (saved in $save_file). " ,
118             "Let the module author know!";
119             } else {
120 0         0 ERROR "Scraper failed to parse HTML, but ",
121             "retrying (",
122             $self->{http_retries_performed} + 1,
123             "/",
124             $self->{http_max_retries},
125             ")";
126 0         0 $self->{http_retries_performed}++;
127             }
128             }
129              
130 0         0 $self->{http_retries_performed} = 0;
131              
132 0 0       0 if($last_item ne $item) {
133 0         0 $last_item = $item;
134 0         0 my $body = "$item $price $self->{http_url}";
135 0         0 $self->{bot}->say(channel => $self->{irc_channel},
136             body => $body);
137 0         0 $self->{bot}->{last_msg} = $body;
138 0         0 INFO "$item \$$price posted to $self->{irc_channel}";
139             } else {
140 0         0 DEBUG "Nothing changed";
141             }
142              
143             } else {
144 0 0       0 if(defined $resp->code()) {
145 0 0       0 ERROR "HTTP fetch failed with code: ", $resp->code(),
146             " ",
147             (defined $resp->message() ? $resp->message() : "");
148             }
149             }
150 0         0 $poe_kernel->delay("http_start", $self->{poll_interval} );
151             },
152             }
153 0         0 );
154             }
155              
156             ###########################################
157             sub scraper_test {
158             ###########################################
159 0     0 0 0 my($self) = @_;
160              
161 0         0 require LWP::UserAgent;
162 0         0 my $ua = LWP::UserAgent->new();
163 0         0 $ua->agent( $self->agent() );
164 0         0 $self->error("");
165              
166 0         0 my $response = $ua->get( $self->woot_url() );
167              
168 0 0       0 if( $response->is_success() ) {
169 0         0 my $bot = Bot::WootOff->new(spawn => 0);
170              
171 0         0 my($item, $price) = $bot->html_scrape( $response->content() );
172 0 0       0 if(!defined $price) {
173 0         0 $self->error("Scraper failed -- please notify the author");
174             # use Sysadm::Install qw( :all );
175             # blurt $response->content(), "test.html";
176 0         0 return undef;
177             } else {
178 0         0 INFO "Scraper successfully got item and price";
179 0         0 INFO "Item: [$item] Price: [$price]";
180 0         0 return($item, $price);
181             }
182             }
183              
184 0         0 $self->error("Fetching woot.com page failed: ", $response->message());
185 0         0 return undef;
186             }
187              
188             ###########################################
189             sub error {
190             ###########################################
191 0     0 0 0 my($self, @text) = @_;
192              
193 0 0       0 if(scalar @text) {
194 0         0 $self->{error} = join ' ', @text;
195 0 0       0 if(length $self->{error}) {
196 0         0 ERROR $self->{error};
197             }
198             }
199              
200 0         0 return $self->{error};
201             }
202              
203             ###########################################
204             sub html_scrape {
205             ###########################################
206 4     4 0 6072 my($self, $html) = @_;
207              
208             # Finds the item and its price in the woot.com HTML page
209 4         10 my($item, $price);
210              
211 4 100       149 if($html =~ m#class="fn">(.*?)(.*?)
    100          
    100          
    50          
212 1         5 ($item, $price) = ($1, $2);
213             } elsif($html =~ m#class="fn">(.*?)\$(.*?)
214 1         7 ($item, $price) = ($1, $2);
215             } elsif( $html =~ m#

(.*?)

\s+

\$(.*?)

#s) {
216             # fall back on legacy format
217 1         7 ($item, $price) = ($1, $2);
218             } elsif($html =~ m#class="title">(.*?)
219             # min/max price ranges
220 1         6 $item = $1;
221 1         3 my @prices = ();
222              
223 1         9 while( $html =~ m#class="price.*?">\$(.*?)
224 2         11 push @prices, $1;
225             }
226              
227 1         3 $price = join " - ", @prices;
228             }
229              
230 4 50       16 if(defined $item) {
231 4         86 $item =~ s/\s+/ /g;
232 4         57 return ($item, $price);
233             }
234              
235 0           WARN "Cannot parse woot HTML";
236 0           return undef;
237             }
238              
239             ###########################################
240             sub run {
241             ###########################################
242 0     0 1   my($self) = @_;
243              
244 0           $self->{bot}->run();
245             }
246              
247             ###########################################
248             sub response_handler {
249             ###########################################
250 0     0 0   my ($request_packet, $response_packet) = @_[ARG0(), ARG1()];
251              
252 0           my $response_object = $response_packet->[0];
253              
254 0           print $response_object->content();
255             }
256              
257             ###########################################
258             ###########################################
259             package Bot::WootOff::Glue;
260             ###########################################
261             ###########################################
262 3     3   6491 use strict;
  3         7  
  3         136  
263 3     3   16 use warnings;
  3         3  
  3         101  
264 3     3   3238 use Bot::BasicBot;
  3         654780  
  3         601  
265 3     3   51 use base qw( Bot::BasicBot );
  3         7  
  3         304  
266 3     3   43 use Log::Log4perl qw(:easy);
  3         7  
  3         41  
267              
268             ###########################################
269             sub said {
270             ###########################################
271 0     0     my($self, $msg) = @_;
272              
273             # If someone says "!woot", repeat the last message
274 0 0         if($msg->{body} =~ /^!woot/) {
275 0 0         return $self->{last_msg} if defined $self->{last_msg};
276             }
277              
278             # remain mum otherwise
279 0           return "";
280             }
281              
282             1;
283              
284             __END__