itunes-connect 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|