itunes-connect 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +86 -0
- data/bin/itunes_connect +27 -0
- data/lib/itunes_connect.rb +5 -0
- data/lib/itunes_connect/commands.rb +41 -0
- data/lib/itunes_connect/commands/download.rb +76 -0
- data/lib/itunes_connect/commands/help.rb +31 -0
- data/lib/itunes_connect/commands/import.rb +35 -0
- data/lib/itunes_connect/commands/report.rb +78 -0
- data/lib/itunes_connect/connection.rb +162 -0
- data/lib/itunes_connect/rc_file.rb +30 -0
- data/lib/itunes_connect/report.rb +56 -0
- data/lib/itunes_connect/store.rb +129 -0
- data/spec/commands/download_spec.rb +140 -0
- data/spec/commands/help_spec.rb +64 -0
- data/spec/commands/import_spec.rb +66 -0
- data/spec/commands/report_spec.rb +195 -0
- data/spec/commands_spec.rb +47 -0
- data/spec/connection_spec.rb +26 -0
- data/spec/fakeweb/homepage +365 -0
- data/spec/fixtures/report.txt +5 -0
- data/spec/report_spec.rb +37 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/store_spec.rb +142 -0
- metadata +146 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Alex Vollmer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
= itunes_connect
|
2
|
+
|
3
|
+
This gem provides a very simple command-line utility and backing
|
4
|
+
"library" (if I can be so bold to use the term in this context) for
|
5
|
+
accessing sales reports from Apple's iTunes Connect website. If you
|
6
|
+
want to automate getting reports out of the App Store, this tool is
|
7
|
+
for you.
|
8
|
+
|
9
|
+
== Usage
|
10
|
+
|
11
|
+
=== Command-Line Usage
|
12
|
+
|
13
|
+
This gem comes with the <tt>itunes_connect</tt> executable which you can use
|
14
|
+
to download reports, import into a sqlite database and report
|
15
|
+
from.
|
16
|
+
|
17
|
+
You can specify the default values for a handful of command-line
|
18
|
+
options by putting them in a file named <tt>.itunesrc</tt> in your
|
19
|
+
home directory. The file is in YAML format and should have the
|
20
|
+
following keys:
|
21
|
+
|
22
|
+
* username
|
23
|
+
* password
|
24
|
+
* database (path to sqlite3 file, optional)
|
25
|
+
|
26
|
+
==== Downloading Reports
|
27
|
+
You can download reports from iTunes Connect using <tt>itunes_connect
|
28
|
+
download</tt>. You may specify your iTunes Connect credentials on
|
29
|
+
the command line _or_ you can put them in YAML format in
|
30
|
+
<tt>~/.itunesrc</tt> with the keys of <tt>:username</tt> and
|
31
|
+
<tt>:password</tt>.
|
32
|
+
|
33
|
+
You can also dump the report to a file (or standard out):
|
34
|
+
|
35
|
+
itunes_connect download -o /tmp/report.txt
|
36
|
+
|
37
|
+
Or you can dump it directly into a sqlite3 database:
|
38
|
+
|
39
|
+
itunes_connect download -b /tmp/report.db
|
40
|
+
|
41
|
+
By default the <tt>download</tt> command will retrieve the most recent
|
42
|
+
daily report. If you have a <tt>database</tt> key in your
|
43
|
+
<tt>~/.itunesrc</tt> file and you _don't_ specify an out file, the
|
44
|
+
report will be automatically imported into the database.
|
45
|
+
|
46
|
+
You can also ask for weekly or monthly reports by using the
|
47
|
+
<tt>-r</tt> command-line option. Note that you can _not_ import a
|
48
|
+
montly report directly into the database because the monthly reports
|
49
|
+
don't have any days associated with the entries.
|
50
|
+
|
51
|
+
Run <tt>itunes_connect help download</tt> for full usage details.
|
52
|
+
|
53
|
+
==== Importing Reports
|
54
|
+
The <tt>import</tt> command allows you to dump an existing report file
|
55
|
+
into the database. This is useful if you've already downloaded a
|
56
|
+
number of reports from iTunes Connect and you just want to put them
|
57
|
+
into the database.
|
58
|
+
|
59
|
+
Run <tt>itunes_connect help import</tt> for full usage details.
|
60
|
+
|
61
|
+
==== Reporting
|
62
|
+
The <tt>report</tt> command queries your database and can produce
|
63
|
+
either detailed, or grouped output. In both cases you can constrain
|
64
|
+
the query to any combination of country, start date and end date.
|
65
|
+
|
66
|
+
Run <tt>itunes_connect help report</tt> for full usage details.
|
67
|
+
|
68
|
+
=== Programmatic Usage
|
69
|
+
|
70
|
+
See the documentation for the ItunesConnect::Connection,
|
71
|
+
ItunesConnect::Report and ItunesConnect::Store classes for details.
|
72
|
+
|
73
|
+
== Note on Patches/Pull Requests
|
74
|
+
|
75
|
+
* Fork the project.
|
76
|
+
* Make your feature addition or bug fix.
|
77
|
+
* Add tests for it. This is important so I don't break it in a
|
78
|
+
future version unintentionally.
|
79
|
+
* Commit, do not mess with rakefile, version, or history.
|
80
|
+
(if you want to have your own version, that is fine but
|
81
|
+
bump version in a commit by itself I can ignore when I pull)
|
82
|
+
* Send me a pull request. Bonus points for topic branches.
|
83
|
+
|
84
|
+
== Copyright
|
85
|
+
|
86
|
+
Copyright (c) 2009 Alex Vollmer. See LICENSE for details.
|
data/bin/itunes_connect
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "itunes_connect"
|
4
|
+
require "clip"
|
5
|
+
|
6
|
+
ItunesConnect::Commands.usage("No command given") if ARGV.empty?
|
7
|
+
|
8
|
+
case command_name = ARGV.shift
|
9
|
+
when '--help', '-h'
|
10
|
+
cli = Clip::Parser.new
|
11
|
+
ItunesConnect::Commands::Help.new(cli).execute!(cli)
|
12
|
+
else
|
13
|
+
cli = ItunesConnect::Commands.default_clip
|
14
|
+
command = ItunesConnect::Commands.for_name(command_name, cli)
|
15
|
+
ItunesConnect::Commands.usage("Unrecognized command '#{command_name}'") if command.nil?
|
16
|
+
begin
|
17
|
+
cli.parse(ARGV)
|
18
|
+
if cli.valid?
|
19
|
+
command.execute!(cli, cli.remainder)
|
20
|
+
else
|
21
|
+
$stderr.puts(cli)
|
22
|
+
end
|
23
|
+
rescue => e
|
24
|
+
$stderr.puts(e.message)
|
25
|
+
$stderr.puts e.backtrace.join("\n") if cli.verbose?
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "itunes_connect/commands/download"
|
2
|
+
require "itunes_connect/commands/import"
|
3
|
+
require "itunes_connect/commands/report"
|
4
|
+
require "itunes_connect/commands/help"
|
5
|
+
require "clip"
|
6
|
+
|
7
|
+
module ItunesConnect::Commands # :nodoc:
|
8
|
+
class << self
|
9
|
+
def for_name(name, clip)
|
10
|
+
self.const_get(name.capitalize.to_sym).new(clip)
|
11
|
+
rescue NameError => e
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def all
|
16
|
+
[Download, Import, Report, Help]
|
17
|
+
end
|
18
|
+
|
19
|
+
def usage(msg)
|
20
|
+
$stderr.puts msg if msg
|
21
|
+
$stderr.puts "USAGE: itunes_connect [command] [options]"
|
22
|
+
ItunesConnect::Commands.all.each do |cmd_cls|
|
23
|
+
cli = Clip do |c|
|
24
|
+
c.banner = "'#{cmd_cls.to_s.split('::').last.downcase}' command options:"
|
25
|
+
|
26
|
+
cmd_cls.new(c)
|
27
|
+
end
|
28
|
+
puts(cli.help)
|
29
|
+
puts
|
30
|
+
end
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def default_clip
|
35
|
+
cli = Clip::Parser.new
|
36
|
+
cli.flag('v', 'verbose', :desc => 'Make output more verbose')
|
37
|
+
cli.flag('g', 'debug', :desc => 'Enable debug output/features (dev only)')
|
38
|
+
cli
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "itunes_connect/rc_file"
|
2
|
+
require "itunes_connect/report"
|
3
|
+
|
4
|
+
module ItunesConnect::Commands
|
5
|
+
class Download # :nodoc:
|
6
|
+
def initialize(c, rcfile=ItunesConnect::RcFile.default)
|
7
|
+
c.opt('u', 'username', :desc => 'iTunes Connect username')
|
8
|
+
c.opt('p', 'password', :desc => 'iTunes Connect password')
|
9
|
+
c.opt('d', 'date', :desc => 'Daily report date (MM/DD/YYYY format)',
|
10
|
+
:default => (Date.today - 1).strftime('%m/%d/%Y'))
|
11
|
+
c.opt('o', 'out', :desc => 'Dump report to file, - is stdout')
|
12
|
+
c.opt('b', 'db', :desc => 'Dump report to sqlite DB at the given path')
|
13
|
+
c.opt('r', 'report',
|
14
|
+
:desc => 'Report type. One of "Daily", "Weekly", "Monthly"',
|
15
|
+
:default => 'Daily') do |r|
|
16
|
+
r.capitalize
|
17
|
+
end
|
18
|
+
@rcfile = rcfile
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute!(opts, args=[])
|
22
|
+
username, password = if opts.username and opts.password
|
23
|
+
[opts.username, opts.password]
|
24
|
+
else
|
25
|
+
[@rcfile.username, @rcfile.password]
|
26
|
+
end
|
27
|
+
|
28
|
+
raise ArgumentError.new("Please provide a username") unless username
|
29
|
+
raise ArgumentError.new("Please provide a password") unless password
|
30
|
+
|
31
|
+
if opts.db and opts.out
|
32
|
+
raise ArgumentError.new("You can only specify :out or :db, not both")
|
33
|
+
end
|
34
|
+
|
35
|
+
if opts.report =~ /^Monthly/ and opts.db
|
36
|
+
raise ArgumentError.new("You cannot persist monthly reports to a " +
|
37
|
+
"database because these reports have no dates " +
|
38
|
+
"associated with them")
|
39
|
+
end
|
40
|
+
|
41
|
+
connection = ItunesConnect::Connection.new(username,
|
42
|
+
password,
|
43
|
+
opts.verbose?,
|
44
|
+
opts.debug?)
|
45
|
+
db = opts.db || @rcfile.database
|
46
|
+
out = if opts.out.nil?
|
47
|
+
db ? StringIO.new : $stdout
|
48
|
+
else
|
49
|
+
opts.out == "-" ? $stdout : File.open(opts.out, "w")
|
50
|
+
end
|
51
|
+
connection.get_report(opts.date || Date.today - 1, out, opts.report)
|
52
|
+
|
53
|
+
if db and StringIO === out
|
54
|
+
$stdout.puts "Importing into database file: #{db}" if opts.verbose?
|
55
|
+
store = ItunesConnect::Store.new(db, opts.verbose?)
|
56
|
+
out.rewind
|
57
|
+
report = ItunesConnect::Report.new(out)
|
58
|
+
count = 0
|
59
|
+
report.each do |entry|
|
60
|
+
count += 1 if store.add(entry.date,
|
61
|
+
entry.country,
|
62
|
+
entry.install_count,
|
63
|
+
entry.upgrade_count)
|
64
|
+
end
|
65
|
+
$stdout.puts "Inserted #{count} rows into #{opts.db}" if opts.verbose?
|
66
|
+
end
|
67
|
+
|
68
|
+
out.flush
|
69
|
+
out.close unless out == $stdout
|
70
|
+
end
|
71
|
+
|
72
|
+
def description
|
73
|
+
"Retrieves reports from the iTunes Connect site"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "itunes_connect/commands"
|
2
|
+
|
3
|
+
module ItunesConnect::Commands
|
4
|
+
class Help # :nodoc:
|
5
|
+
def initialize(c)
|
6
|
+
# nothing to do here
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute!(opts={ }, args=[], out=$stdout)
|
10
|
+
if args.empty?
|
11
|
+
out.puts "Available commands:"
|
12
|
+
out.puts
|
13
|
+
ItunesConnect::Commands.all.each do |cmd|
|
14
|
+
out.printf("%-9s %s\n",
|
15
|
+
cmd.to_s.split('::').last.downcase,
|
16
|
+
cmd.new(Clip::Parser.new).description)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
cli = ItunesConnect::Commands.default_clip
|
20
|
+
cmd = ItunesConnect::Commands.for_name(args.first, cli)
|
21
|
+
cli.banner = "Command options for '#{cmd.class.to_s.split('::').last.downcase}':"
|
22
|
+
raise ArgumentError.new("Unrecognized command '#{args.first}'") if cmd.nil?
|
23
|
+
out.puts(cli.help)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def description
|
28
|
+
"Describe a particular command"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "itunes_connect/rc_file"
|
2
|
+
require "itunes_connect/report"
|
3
|
+
|
4
|
+
module ItunesConnect::Commands
|
5
|
+
class Import # :nodoc:
|
6
|
+
def initialize(c, rcfile=ItunesConnect::RcFile.default)
|
7
|
+
c.opt('b', 'db', :desc => 'Dump report to sqlite DB at the given path')
|
8
|
+
c.req('f', 'file', :desc => 'The file to import, - means standard in')
|
9
|
+
@rcfile = rcfile
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute!(opts, args=[])
|
13
|
+
db = opts.db || @rcfile.database || nil
|
14
|
+
raise ArgumentError.new("Missing :db option") unless db
|
15
|
+
raise ArgumentError.new("Missing :file option") if opts.file.nil?
|
16
|
+
store = ItunesConnect::Store.new(db, opts.verbose?)
|
17
|
+
input = opts.file == '-' ? $stdin : open(opts.file, 'r')
|
18
|
+
count = 0
|
19
|
+
ItunesConnect::Report.new(input).each do |entry|
|
20
|
+
count += 1 if store.add(entry.date,
|
21
|
+
entry.country,
|
22
|
+
entry.install_count,
|
23
|
+
entry.upgrade_count)
|
24
|
+
end
|
25
|
+
|
26
|
+
if opts.verbose?
|
27
|
+
$stdout.puts "Added #{count} rows to the database"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def description
|
32
|
+
"Imports report data into a database file"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "itunes_connect/rc_file"
|
2
|
+
require "itunes_connect/store"
|
3
|
+
|
4
|
+
module ItunesConnect::Commands
|
5
|
+
class Report # :nodoc:
|
6
|
+
def initialize(c, rcfile=ItunesConnect::RcFile.default)
|
7
|
+
c.opt('b', 'db', :desc => 'Dump report to sqlite DB at the given path')
|
8
|
+
c.opt('c', 'country',
|
9
|
+
:desc => 'A two-letter country code to filter results with')
|
10
|
+
c.opt('f', 'from', :desc => 'The starting date, inclusive') do |f|
|
11
|
+
Date.parse(f)
|
12
|
+
end
|
13
|
+
c.opt('t', 'to', :desc => 'The ending date, inclusive') do |t|
|
14
|
+
Date.parse(t)
|
15
|
+
end
|
16
|
+
c.flag('s', 'summarize', :desc => 'Summarize results by country code')
|
17
|
+
c.flag('n', 'no-header', :desc => 'Suppress the column headers on output')
|
18
|
+
c.opt('d', 'delimiter',
|
19
|
+
:desc => 'The delimiter to use for output (normally TAB)',
|
20
|
+
:default => "\t")
|
21
|
+
c.flag('o', 'total', :desc => 'Add totals at the end of the report')
|
22
|
+
@rcfile = rcfile
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute!(opts, args=[], out=$stdout)
|
26
|
+
db = opts.db || @rcfile.database || nil
|
27
|
+
raise ArgumentError.new("Missing :db option") if db.nil?
|
28
|
+
store = ItunesConnect::Store.new(db)
|
29
|
+
params = {
|
30
|
+
:to => opts.to,
|
31
|
+
:from => opts.from,
|
32
|
+
:country => opts.country
|
33
|
+
}
|
34
|
+
|
35
|
+
total_installs, total_upgrades = 0, 0
|
36
|
+
|
37
|
+
unless opts.no_header?
|
38
|
+
out.puts([opts.summarize? ? nil : "Date",
|
39
|
+
"Country",
|
40
|
+
"Installs",
|
41
|
+
"Upgrades"
|
42
|
+
].compact.join(opts.delimiter))
|
43
|
+
end
|
44
|
+
|
45
|
+
if opts.summarize?
|
46
|
+
store.country_counts(params).each do |x|
|
47
|
+
out.puts [x.country,
|
48
|
+
x.install_count,
|
49
|
+
x.update_count].join(opts.delimiter)
|
50
|
+
total_installs += x.install_count
|
51
|
+
total_upgrades += x.update_count
|
52
|
+
end
|
53
|
+
else
|
54
|
+
store.counts(params).each do |x|
|
55
|
+
out.puts [x.report_date,
|
56
|
+
x.country,
|
57
|
+
x.install_count,
|
58
|
+
x.update_count].join(opts.delimiter)
|
59
|
+
total_installs += x.install_count
|
60
|
+
total_upgrades += x.update_count
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if opts.total?
|
65
|
+
out.puts ["Total",
|
66
|
+
opts.summarize? ? nil : "-",
|
67
|
+
total_installs,
|
68
|
+
total_upgrades
|
69
|
+
].compact.join(opts.delimiter)
|
70
|
+
end
|
71
|
+
out.flush
|
72
|
+
end
|
73
|
+
|
74
|
+
def description
|
75
|
+
"Generates reports from a local database"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require "digest/md5"
|
2
|
+
require "tempfile"
|
3
|
+
require "yaml"
|
4
|
+
require "zlib"
|
5
|
+
require "rubygems"
|
6
|
+
require "httpclient"
|
7
|
+
require "nokogiri"
|
8
|
+
|
9
|
+
module ItunesConnect
|
10
|
+
|
11
|
+
# Abstracts the iTunes Connect website.
|
12
|
+
# Implementation inspired by
|
13
|
+
# http://code.google.com/p/itunes-connect-scraper/
|
14
|
+
class Connection
|
15
|
+
|
16
|
+
REPORT_PERIODS = ["Monthly Free", "Weekly", "Daily"]
|
17
|
+
|
18
|
+
BASE_URL = 'https://itts.apple.com' # :nodoc:
|
19
|
+
REFERER_URL = 'https://itts.apple.com/cgi-bin/WebObjects/Piano.woa' # :nodoc:
|
20
|
+
|
21
|
+
# Create a new instance with the username and password used to sign
|
22
|
+
# in to the iTunes Connect website
|
23
|
+
def initialize(username, password, verbose=false, debug=false)
|
24
|
+
@username, @password = username, password
|
25
|
+
@verbose = verbose
|
26
|
+
@debug = debug
|
27
|
+
end
|
28
|
+
|
29
|
+
def verbose? # :nodoc:
|
30
|
+
!!@verbose
|
31
|
+
end
|
32
|
+
|
33
|
+
def debug? # :nodoc:
|
34
|
+
!!@debug
|
35
|
+
end
|
36
|
+
|
37
|
+
# Retrieve a report from iTunes Connect. This method will return the
|
38
|
+
# raw report file as a String. If specified, the <tt>date</tt>
|
39
|
+
# parameter should be a <tt>Date</tt> instance, and the
|
40
|
+
# <tt>period</tt> parameter must be one of the values identified
|
41
|
+
# in the <tt>REPORT_PERIODS</tt> array, or this method will raise
|
42
|
+
# and <tt>ArgumentError</tt>.
|
43
|
+
#
|
44
|
+
# Any dates given that equal the current date or newer will cause
|
45
|
+
# this method to raise an <tt>ArgumentError</tt>.
|
46
|
+
#
|
47
|
+
def get_report(date, out, period='Daily')
|
48
|
+
date = Date.parse(date) if String === date
|
49
|
+
if date >= Date.today
|
50
|
+
raise ArgumentError, "You must specify a date before today"
|
51
|
+
end
|
52
|
+
|
53
|
+
period = 'Monthly Free' if period == 'Monthly'
|
54
|
+
unless REPORT_PERIODS.member?(period)
|
55
|
+
raise ArgumentError, "'period' must be one of #{REPORT_PERIODS.join(', ')}"
|
56
|
+
end
|
57
|
+
|
58
|
+
# grab the home page
|
59
|
+
doc = Nokogiri::HTML(get_content(REFERER_URL))
|
60
|
+
login_path = (doc/"form/@action").to_s
|
61
|
+
|
62
|
+
# login
|
63
|
+
doc = Nokogiri::HTML(get_content(login_path, {
|
64
|
+
'theAccountName' => @username,
|
65
|
+
'theAccountPW' => @password,
|
66
|
+
'1.Continue.x' => '36',
|
67
|
+
'1.Continue.y' => '17',
|
68
|
+
'theAuxValue' => ''
|
69
|
+
}))
|
70
|
+
|
71
|
+
report_url = (doc / "//*[@name='frmVendorPage']/@action").to_s
|
72
|
+
report_type_name = (doc / "//*[@id='selReportType']/@name").to_s
|
73
|
+
date_type_name = (doc / "//*[@id='selDateType']/@name").to_s
|
74
|
+
|
75
|
+
# handle first report form
|
76
|
+
doc = Nokogiri::HTML(get_content(report_url, {
|
77
|
+
report_type_name => 'Summary',
|
78
|
+
date_type_name => period,
|
79
|
+
'hiddenDayOrWeekSelection' => period,
|
80
|
+
'hiddenSubmitTypeName' => 'ShowDropDown'
|
81
|
+
}))
|
82
|
+
report_url = (doc / "//*[@name='frmVendorPage']/@action").to_s
|
83
|
+
report_type_name = (doc / "//*[@id='selReportType']/@name").to_s
|
84
|
+
date_type_name = (doc / "//*[@id='selDateType']/@name").to_s
|
85
|
+
date_name = (doc / "//*[@id='dayorweekdropdown']/@name").to_s
|
86
|
+
|
87
|
+
# now get the report
|
88
|
+
date_str = case period
|
89
|
+
when 'Daily'
|
90
|
+
date.strftime("%m/%d/%Y")
|
91
|
+
when 'Weekly', 'Monthly Free'
|
92
|
+
date = (doc / "//*[@id='dayorweekdropdown']/option").find do |d|
|
93
|
+
d1, d2 = d.text.split(' To ').map { |x| Date.parse(x) }
|
94
|
+
date >= d1 and date <= d2
|
95
|
+
end[:value] rescue nil
|
96
|
+
end
|
97
|
+
|
98
|
+
raise ArgumentError, "No reports are available for that date" unless date_str
|
99
|
+
|
100
|
+
report = get_content(report_url, {
|
101
|
+
report_type_name => 'Summary',
|
102
|
+
date_type_name => period,
|
103
|
+
date_name => date_str,
|
104
|
+
'download' => 'Download',
|
105
|
+
'hiddenDayOrWeekSelection' => date_str,
|
106
|
+
'hiddenSubmitTypeName' => 'Download'
|
107
|
+
})
|
108
|
+
|
109
|
+
begin
|
110
|
+
gunzip = Zlib::GzipReader.new(StringIO.new(report))
|
111
|
+
out << gunzip.read
|
112
|
+
rescue => e
|
113
|
+
doc = Nokogiri::HTML(report)
|
114
|
+
msg = (doc / "//font[@id='iddownloadmsg']").text.strip
|
115
|
+
$stderr.puts "Unable to download the report, reason:"
|
116
|
+
$stderr.puts msg.strip
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def client
|
123
|
+
@client ||= client = HTTPClient.new
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_content(uri, query=nil, headers={ })
|
127
|
+
$stdout.puts "Querying #{uri} with #{query.inspect}" if self.debug?
|
128
|
+
if @referer
|
129
|
+
headers = {
|
130
|
+
'Referer' => @referer,
|
131
|
+
'User-Agent' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-us) AppleWebKit/531.9 (KHTML, like Gecko) Version/4.0.3 Safari/531.9'
|
132
|
+
}.merge(headers)
|
133
|
+
end
|
134
|
+
url = case uri
|
135
|
+
when /^https?:\/\//
|
136
|
+
uri
|
137
|
+
else
|
138
|
+
BASE_URL + uri
|
139
|
+
end
|
140
|
+
|
141
|
+
response = client.get(url, query, headers)
|
142
|
+
|
143
|
+
if self.debug?
|
144
|
+
md5 = Digest::MD5.new; md5 << url; md5 << Time.now.to_s
|
145
|
+
path = File.join(Dir.tmpdir, md5.to_s + ".html")
|
146
|
+
out = open(path, "w") do |f|
|
147
|
+
f << "Status: #{response.status}\n"
|
148
|
+
f << response.header.all.map do |name, value|
|
149
|
+
"#{name}: #{value}"
|
150
|
+
end.join("\n")
|
151
|
+
f << "\n\n"
|
152
|
+
f << response.body.dump
|
153
|
+
end
|
154
|
+
puts "#{url} -> #{path}"
|
155
|
+
end
|
156
|
+
|
157
|
+
@referer = url
|
158
|
+
response.body.dump
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|