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 =~ /(\d+)GB<\/span>/s; |
196
|
0
|
|
|
|
|
|
my ($remaining) = $html =~ /(\d+)GB<\/span>/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__ |