File Coverage

blib/lib/Net/Comcast/Customer.pm
Criterion Covered Total %
statement 15 71 21.1
branch 0 16 0.0
condition 0 18 0.0
subroutine 5 14 35.7
pod 7 7 100.0
total 27 126 21.4


line stmt bran cond sub pod time code
1             package Net::Comcast::Customer;
2 1     1   31811 use strict;
  1         4  
  1         44  
3 1     1   7 use warnings;
  1         2  
  1         36  
4 1     1   6 use Carp;
  1         7  
  1         90  
5 1     1   38689 use WWW::Mechanize;
  1         967828  
  1         44  
6 1     1   9480 use Date::Calc qw( Days_in_Month );
  1         99557  
  1         932  
7              
8             =head1 NAME
9              
10             Net::Comcast::Customer - Comcast Customer Central web interface
11              
12             =head1 VERSION
13              
14             Version 1.2
15              
16             =cut
17              
18             our $VERSION = '1.2';
19              
20             =head1 SYNOPSIS
21              
22             Access Comcast's Customer website.
23              
24              
25             use Net::Comcast::Customer;
26              
27             my $c = Net::Comcast::Customer->new();
28             $c->login('username', 'pa$$word');
29            
30             # Get data usage in gigabytes.
31             my $usage = $c->get_usage;
32             my $budgeted = $c->get_budgeted_usage;
33             ...
34              
35             =head1 DESCRIPTION
36              
37             Comcast Customer Central is "The one place where you can view and pay your bill, and manage all your Comcast product features and settings." Since Comcast has a 250 GB/month data cap, this module will allow you to view your total bandwidth used and your current "budgeted" bandwidth. The data is suitable for exporting into monitoring tools like RRDtool and Cacti.
38              
39             In other words, this module is a programmatic interface to what you can see at L .
40              
41             This module could do much more (patches welcome). Also, Comcast apparently breaks this all the time, so good luck!
42              
43             =cut
44              
45             # Comcast constants
46             my $LOGIN_URL = 'https://customer.comcast.com/Secure/Home.aspx';
47             my $USAGE_URL = 'https://customer.comcast.com/Secure/UsageMeterDetail.aspx';
48             my $USER_AGENT = 'Mozilla/5.0 (X11; Linux i686; rv:12.0) Gecko/20100101 Firefox/12.0';
49              
50             =head1 METHODS
51              
52             =head2 new
53              
54             No args, just create and go.
55              
56             =cut
57              
58             sub new {
59 0     0 1   my $class = shift;
60 0           my $self = {
61             'mech' => WWW::Mechanize->new(
62             agent => $USER_AGENT,
63             ),
64             # Monthly GB limit
65             # This was hard to scrape reliably from the HTML, so I'm
66             # hardcoding here.
67             'max_gb' => 250,
68             'debug' => 0,
69             };
70 0           bless($self, $class);
71 0           return $self;
72             }
73              
74             =head2 debug
75              
76             Get/Set the debug level. Takes one argument: an integer.
77              
78             Use this if you're having problems. Set this to zero to silence all debugging (the default).
79              
80             Returns an integer.
81              
82             =cut
83              
84             sub debug {
85 0     0 1   my $self = shift;
86 0 0         if (@_) { $self->{'debug'} = shift; }
  0            
87             # Custom debugging for WWW::Mechanize
88 0 0         if ($self->{'debug'} > 1) {
89 0     0     $self->mech->add_handler("request_send", sub { shift->dump; return });
  0            
  0            
90             }
91 0           return $self->{'debug'};
92             }
93              
94              
95             =head2 mech
96              
97             WWW::Mech accessor. You probably won't need to use this in your own code.
98              
99             Returns a WWW::Mechanize object.
100              
101             =cut
102              
103             sub mech {
104 0     0 1   my $self = shift;
105 0 0         if (@_) { $self->{'mech'} = shift; }
  0            
106 0           return $self->{'mech'};
107             }
108              
109              
110             =head2 max_gb
111              
112             Monthly Gigabyte limit accessor. Defaults to 250.
113              
114             Comcast has plans to change this to 300 in the future. Read more on the L<"Comcast blog entry"|http://blog.comcast.com/2012/05/comcast-to-replace-usage-cap-with-improved-data-usage-management-approaches.html> and L<"Comcast FAQ page"|https://customer.comcast.com/help-and-support/internet/data-usage-what-are-the-different-plans-launching/> .
115              
116             Returns an integer.
117              
118             =cut
119              
120             sub max_gb {
121 0     0 1   my $self = shift;
122 0 0         if (@_) { $self->{'max_gb'} = shift; }
  0            
123 0           return $self->{'max_gb'};
124             }
125              
126              
127             =head2 login
128              
129             Log in to Comcast's customer service portal. Takes two arguments: a username string and a password string.
130              
131             =cut
132              
133             sub login {
134 0     0 1   my $self = shift;
135 0   0       my $username = shift || croak("missing username arg");
136 0   0       my $password = shift || croak("missing password arg");
137              
138             # Load the login page.
139 0           $self->mech->get( $LOGIN_URL );
140             # TODO: Error-check (network conn) here.
141              
142 0           $self->mech->submit_form(
143             form_name => 'signin',
144             fields => {
145             user => $username,
146             passwd => $password,
147             }
148             );
149              
150             # After submitting the login form, we're taken to a page "Retrieving
151             # your account information, one moment please..." with a Flash app.
152             # The page has a "redir" form with a "cima.ticket" value. Javascript
153             # submits this form when the page is loaded. We do it here manually.
154              
155             # Before we switched to $LOGIN_URL:
156             # This will be a POST to $USAGE_URL, which will then 302 Redirect to
157             # Preload.aspx.
158 0           $self->mech->submit_form(
159             form_name => 'redir',
160             );
161              
162             # We're now at some sort of ASP.net-related page ("Preload.aspx").
163             # We have to append "preload=true" and load it a second time in order
164             # to continue.
165             # (Maybe there's a more elegant way to do this?)
166 0           $self->mech->get( $self->mech->uri . '&preload=true' );
167              
168             # Now we can get whatever page we want.
169             }
170              
171              
172             =head2 get_usage
173              
174             Get your data usage in GB. You must log in first with login().
175              
176             Returns an integer, or undef if the data could not be found.
177              
178             =cut
179              
180             sub get_usage {
181 0     0 1   my $self = shift;
182             # TODO: check if we're actually logged in.
183             # Load the Usage page.
184 0           $self->mech->get( $USAGE_URL );
185             # Pull the usage data from the HTML.
186 0           return $self->_get_usage_from_content($self->mech->content)
187             }
188              
189             # Extract the usage data from HTML.
190             sub _get_usage_from_content {
191 0     0     my $self = shift;
192 0   0       my $html = shift || croak("HTML content argument missing");
193              
194             # These GB values are integers, or "<1" for "less than one GB".
195 0           my ($used) = $html =~ /(/s;
196 0           my ($remaining) = $html =~ //s;
197            
198             # Get rid of that pesky less-than sign.
199 0 0 0       if($used && $used eq '<1') {
200 0           $used = 0;
201             }
202 0 0 0       if($remaining && $remaining eq '<1') {
203 0           $remaining = 0;
204             }
205              
206             # Sanity check
207 0 0 0       if (!defined($used) && $self->debug > 0) {
208 0           carp("could not find usage data in HTML.");
209             # Try to find the chunk of HTML that generally has what we need.
210 0           my ($dataused) = $html =~ /
/s;
211 0 0         if ($dataused) {
212 0           carp($dataused);
213             } else {
214             # Just print the entire thing.
215 0           carp($html);
216             }
217             }
218              
219 0           return $used;
220             }
221              
222             =head2 get_budgeted_usage
223              
224             Get your budgeted data usage in GB. For planning purposes, you'll want to correlate this value with what you get from get_usage().
225              
226             Each month Comcast resets their counters to zero. If your cap is 250 GB/month, then on the first day of the month, you should use about 8 GB. After the second day of the month, your usage should be up to 16 GB. After the third day, 24 GB. And so on...
227              
228             This get_budgeted_usage() method does the math for you. Using localtime(), it will figure out how much bandwidth you should have used B. If you graph this value, it will give you a trend line that will help you know how well you're doing at staying under your limit.
229              
230             This method returns a value with a resolution of one hour. Remember that Comcast says their system is delayed up to three hours. If you suddenly download down 10GB of data, it may not show up on their site's meter until three hours later.
231              
232             Pure math, no HTTP involved.
233              
234             Returns a floating point value.
235              
236             =cut
237              
238             sub get_budgeted_usage {
239 0     0 1   my $self = shift;
240              
241             # Get today's date info.
242 0           my (undef, undef, $hour, $day, $month, $year) = localtime;
243 0           $month++;
244 0           $year += 1900;
245             # Get the number of hours elapsed so far.
246 0           my $hours = $hour + ($day * 24);
247             # Get the total number of hours in this month.
248 0           my $days_in_month = Days_in_Month($year, $month);
249 0           my $hours_in_month = $days_in_month * 24;
250             # Divide "now" by the total number of hours.
251 0           my $fraction = $hours / $hours_in_month;
252             # Find out our budgeted GB value.
253 0           my $budgeted_gb = $self->max_gb * $fraction;
254 0           $budgeted_gb = sprintf("%.3f", $budgeted_gb);
255 0           return $budgeted_gb;
256             }
257              
258             1;
259             __END__