/
connection.rb
162 lines (138 loc) · 5.61 KB
/
connection.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
require "digest/md5"
require "tempfile"
require "yaml"
require "zlib"
require "rubygems"
require "httpclient"
require "nokogiri"
module ItunesConnect
# Abstracts the iTunes Connect website.
# Implementation inspired by
# http://code.google.com/p/itunes-connect-scraper/
class Connection
REPORT_PERIODS = ["Monthly Free", "Weekly", "Daily"]
BASE_URL = 'https://itts.apple.com' # :nodoc:
REFERER_URL = 'https://itts.apple.com/cgi-bin/WebObjects/Piano.woa' # :nodoc:
# Create a new instance with the username and password used to sign
# in to the iTunes Connect website
def initialize(username, password, verbose=false, debug=false)
@username, @password = username, password
@verbose = verbose
@debug = debug
end
def verbose? # :nodoc:
!!@verbose
end
def debug? # :nodoc:
!!@debug
end
# Retrieve a report from iTunes Connect. This method will return the
# raw report file as a String. If specified, the <tt>date</tt>
# parameter should be a <tt>Date</tt> instance, and the
# <tt>period</tt> parameter must be one of the values identified
# in the <tt>REPORT_PERIODS</tt> array, or this method will raise
# and <tt>ArgumentError</tt>.
#
# Any dates given that equal the current date or newer will cause
# this method to raise an <tt>ArgumentError</tt>.
#
def get_report(date, out, period='Daily')
date = Date.parse(date) if String === date
if date >= Date.today
raise ArgumentError, "You must specify a date before today"
end
period = 'Monthly Free' if period == 'Monthly'
unless REPORT_PERIODS.member?(period)
raise ArgumentError, "'period' must be one of #{REPORT_PERIODS.join(', ')}"
end
# grab the home page
doc = Nokogiri::HTML(get_content(REFERER_URL))
login_path = (doc/"form/@action").to_s
# login
doc = Nokogiri::HTML(get_content(login_path, {
'theAccountName' => @username,
'theAccountPW' => @password,
'1.Continue.x' => '36',
'1.Continue.y' => '17',
'theAuxValue' => ''
}))
report_url = (doc / "//*[@name='frmVendorPage']/@action").to_s
report_type_name = (doc / "//*[@id='selReportType']/@name").to_s
date_type_name = (doc / "//*[@id='selDateType']/@name").to_s
# handle first report form
doc = Nokogiri::HTML(get_content(report_url, {
report_type_name => 'Summary',
date_type_name => period,
'hiddenDayOrWeekSelection' => period,
'hiddenSubmitTypeName' => 'ShowDropDown'
}))
report_url = (doc / "//*[@name='frmVendorPage']/@action").to_s
report_type_name = (doc / "//*[@id='selReportType']/@name").to_s
date_type_name = (doc / "//*[@id='selDateType']/@name").to_s
date_name = (doc / "//*[@id='dayorweekdropdown']/@name").to_s
# now get the report
date_str = case period
when 'Daily'
date.strftime("%m/%d/%Y")
when 'Weekly', 'Monthly Free'
date = (doc / "//*[@id='dayorweekdropdown']/option").find do |d|
d1, d2 = d.text.split(' To ').map { |x| Date.parse(x) }
date >= d1 and date <= d2
end[:value] rescue nil
end
raise ArgumentError, "No reports are available for that date" unless date_str
report = get_content(report_url, {
report_type_name => 'Summary',
date_type_name => period,
date_name => date_str,
'download' => 'Download',
'hiddenDayOrWeekSelection' => date_str,
'hiddenSubmitTypeName' => 'Download'
})
begin
gunzip = Zlib::GzipReader.new(StringIO.new(report))
out << gunzip.read
rescue => e
doc = Nokogiri::HTML(report)
msg = (doc / "//font[@id='iddownloadmsg']").text.strip
$stderr.puts "Unable to download the report, reason:"
$stderr.puts msg.strip
end
end
private
def client
@client ||= client = HTTPClient.new
end
def get_content(uri, query=nil, headers={ })
$stdout.puts "Querying #{uri} with #{query.inspect}" if self.debug?
if @referer
headers = {
'Referer' => @referer,
'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'
}.merge(headers)
end
url = case uri
when /^https?:\/\//
uri
else
BASE_URL + uri
end
response = client.get(url, query, headers)
if self.debug?
md5 = Digest::MD5.new; md5 << url; md5 << Time.now.to_s
path = File.join(Dir.tmpdir, md5.to_s + ".html")
out = open(path, "w") do |f|
f << "Status: #{response.status}\n"
f << response.header.all.map do |name, value|
"#{name}: #{value}"
end.join("\n")
f << "\n\n"
f << response.body.dump
end
puts "#{url} -> #{path}"
end
@referer = url
response.body.dump
end
end
end