sales 0.0.1
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/Autoingestion.class +0 -0
- data/README.md +19 -0
- data/Rakefile +22 -0
- data/VERSION.yml +5 -0
- data/bin/sale +421 -0
- data/sales.yml +4 -0
- metadata +59 -0
data/Autoingestion.class
ADDED
Binary file
|
data/README.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
sales
|
2
|
+
=====
|
3
|
+
|
4
|
+
iTunes Connect Command Line Autoingestion Script. Besides downloading, also computes and presents totals.
|
5
|
+
|
6
|
+
usage
|
7
|
+
=====
|
8
|
+
|
9
|
+
in the directory where You want to download Your iTunes Connect reports, run _sale_.
|
10
|
+
this will copy a file called _sales.yml_ into that directory.
|
11
|
+
open _sales.yml_ and fill in Your iTunes Connect credentials:
|
12
|
+
|
13
|
+
:username: kitschmaster@gmail.com #iTunes connect username
|
14
|
+
:password: yourpassword #iTunes Connect password
|
15
|
+
:vendorId: 80076733 #iTunes Connect -> Sales and Trends, find the vendorId on the header of the table next to the company name
|
16
|
+
|
17
|
+
with the credentials in place run _sale_ again, it should now download the latest daily report and present it.
|
18
|
+
|
19
|
+
run `sale help` to get a list of all possible commands.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gem|
|
6
|
+
gem.name = "sales"
|
7
|
+
gem.summary = %Q{iTunes Connect Command Line Autoingestion Script.}
|
8
|
+
gem.email = "kitschmaster@gmail.com"
|
9
|
+
gem.homepage = "http://github.com/mihael/sales"
|
10
|
+
gem.authors = ["Mihael"]
|
11
|
+
gem.rubyforge_project = "Sales"
|
12
|
+
gem.description = %Q{iTunes Connect Command Line Autoingestion Script. Computes and presents totals. Uses Autoingestion.class for report downloading.}
|
13
|
+
gem.files = FileList['bin/*', '[A-Z]*', 'sales.yml'].to_a # 'lib/**/*.*', 'test/**/*'
|
14
|
+
gem.executables = ['sale']
|
15
|
+
gem.default_executable = 'sale'
|
16
|
+
#gem.add_dependency('haml')
|
17
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
|
+
end
|
19
|
+
#Jeweler::RubyforgeTasks.new
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
22
|
+
end
|
data/VERSION.yml
ADDED
data/bin/sale
ADDED
@@ -0,0 +1,421 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Sales
|
4
|
+
|
5
|
+
#make nice colors for terminal strings
|
6
|
+
class String
|
7
|
+
def red; colorize(self, "\e[1m\e[31m"); end
|
8
|
+
def green; colorize(self, "\e[1m\e[32m"); end
|
9
|
+
def dark_green; colorize(self, "\e[32m"); end
|
10
|
+
def yellow; colorize(self, "\e[1m\e[33m"); end
|
11
|
+
def blue; colorize(self, "\e[1m\e[34m"); end
|
12
|
+
def dark_blue; colorize(self, "\e[34m"); end
|
13
|
+
def pur; colorize(self, "\e[1m\e[35m"); end
|
14
|
+
def colorize(text, color_code) "#{color_code}#{text}\e[0m" end
|
15
|
+
def clean; self.gsub(/\n|\t|\r/, ' ').gsub(/[\(\)\/_-]/, ' ').squeeze(' ').strip end
|
16
|
+
end
|
17
|
+
|
18
|
+
#the usage of this program
|
19
|
+
USAGE = <<-EGASU
|
20
|
+
|
21
|
+
getting daily reports for the last 14 days:
|
22
|
+
#{'sale get:daily'.green}
|
23
|
+
|
24
|
+
getting daily reports for the last 90 days:
|
25
|
+
#{'sale get:weekly'.green}
|
26
|
+
|
27
|
+
compute alltime reports
|
28
|
+
#{'sale daily'.green}
|
29
|
+
#{'sale weekly'.green}
|
30
|
+
|
31
|
+
get and present the last daily summary report
|
32
|
+
#{'sale'}.green
|
33
|
+
|
34
|
+
get and present a daily summary report for a date
|
35
|
+
#{'sale.rb YYYYMMDD'.green}
|
36
|
+
|
37
|
+
EGASU
|
38
|
+
|
39
|
+
require 'rubygems'
|
40
|
+
require 'open-uri'
|
41
|
+
require 'json'
|
42
|
+
require 'date'
|
43
|
+
require 'yaml'
|
44
|
+
|
45
|
+
V = YAML.load_file(File.join(File.dirname(__FILE__), %w[.. VERSION.yml]))
|
46
|
+
VERSION = "#{V[:major]}.#{V[:minor]}.#{V[:patch]}"
|
47
|
+
|
48
|
+
#load the iTunes Connect credentials from the sales.yml
|
49
|
+
example_sales_yml_file = File.join(File.dirname(__FILE__), %w[.. sales.yml])
|
50
|
+
dest_sales_yml_file = "sales.yml"
|
51
|
+
if File.exists? dest_sales_yml_file
|
52
|
+
S = YAML.load_file("sales.yml")
|
53
|
+
username = S[:username]
|
54
|
+
password = S[:password]
|
55
|
+
vendorId = S[:vendorId]
|
56
|
+
else
|
57
|
+
puts "Please fill out Your iTunes Connect credentials in the".red + " sales.yml ".green + "file in the same dir where You run 'sale'.".red
|
58
|
+
`cp #{example_sales_yml_file} sales.yml; ls -al` unless File.exists? dest_sales_yml_file
|
59
|
+
abort
|
60
|
+
end
|
61
|
+
|
62
|
+
#prepare the java runtime classpath for the Autoingestion.class
|
63
|
+
classpath = File.join(File.dirname(__FILE__), %w[..])
|
64
|
+
|
65
|
+
#identifiers
|
66
|
+
PRODUCT_TYPE_IDENTIFIER = {
|
67
|
+
"1" => "Free or Paid Apps, iPhone and iPod Touch",
|
68
|
+
"7" => "Updates, iPhone and iPod Touch",
|
69
|
+
"IA1" => "In Apps Purchase",
|
70
|
+
"IA9" => "In Apps Subscription",
|
71
|
+
"IAY" => "Auto-Renewable Subscription",
|
72
|
+
"1F" => "Free or Paid Apps (Universal)",
|
73
|
+
"7F" => "Updates (Universal)",
|
74
|
+
"1T" => "Free or Paid Apps, iPad",
|
75
|
+
"7T" => "Updates, iPad",
|
76
|
+
"F1" => "Free or Paid Apps, Mac OS",
|
77
|
+
"F7" => "Updates, Mac OS",
|
78
|
+
"FI1" => "In Apps Purchase, Mac OS",
|
79
|
+
"1E" => "Custome iPhone and iPod Touch",
|
80
|
+
"1EP" => "Custome iPad",
|
81
|
+
"1EU" => "Custome Universal"
|
82
|
+
}
|
83
|
+
SALE_IDENTS = ["1", "1F", "1T", "F1"]
|
84
|
+
INAPP_SALE_IDENTS = ["IA1", "IA9", "IAY", "FI1"]
|
85
|
+
UPDATE_IDENTS = ["7", "7F", "7T", "F7"]
|
86
|
+
|
87
|
+
#the parser was copied from github, https://github.com/siuying/itunes-auto-ingestion/blob/master/lib/itunes_ingestion/sales_report_parser.rb
|
88
|
+
class SalesReportParser
|
89
|
+
# Parse sales report
|
90
|
+
#
|
91
|
+
# report - text based report form itunesconnect
|
92
|
+
#
|
93
|
+
# Returns array of hash, each hash contains one line of sales report
|
94
|
+
def self.parse(report)
|
95
|
+
lines = report.split("\n")
|
96
|
+
#puts "lines: #{lines}"
|
97
|
+
header = lines.shift # remove first line
|
98
|
+
lines.collect do |line|
|
99
|
+
#puts "line: #{line}"
|
100
|
+
provider, country, sku, developer, title, version, product_type_id, units, developer_proceeds, begin_date, end_date, currency, country_code, currency_of_proceeds, apple_id, customer_price, promo_code, parent_id, subscription, period = line.split("\t")
|
101
|
+
p = {
|
102
|
+
:provider => provider.strip,
|
103
|
+
:country => country.strip,
|
104
|
+
:sku => sku.strip,
|
105
|
+
:developer => developer.strip,
|
106
|
+
:title => title.strip,
|
107
|
+
:version => version.strip,
|
108
|
+
:product_type_id => product_type_id.strip,
|
109
|
+
:units => units.to_i,
|
110
|
+
:developer_proceeds => developer_proceeds.to_f,
|
111
|
+
:begin_date => Date.strptime(begin_date.strip, '%m/%d/%Y'),
|
112
|
+
:end_date => Date.strptime(end_date.strip, '%m/%d/%Y'),
|
113
|
+
:currency => currency.strip,
|
114
|
+
:country_code => country_code.strip,
|
115
|
+
:currency_of_proceeds => currency_of_proceeds.strip,
|
116
|
+
:apple_id => apple_id.to_i,
|
117
|
+
:customer_price => customer_price.to_f,
|
118
|
+
:promo_code => promo_code.strip,
|
119
|
+
:parent_id => parent_id.strip,
|
120
|
+
:subscription => subscription.strip,
|
121
|
+
:period => period
|
122
|
+
}
|
123
|
+
puts "parsing failed".red if p==nil
|
124
|
+
p
|
125
|
+
end #lines collect
|
126
|
+
end #self.parse
|
127
|
+
end #class
|
128
|
+
|
129
|
+
puts "\nSales v#{VERSION}".red + " created by Your Headless Standup Programmer http://kitschmaster.com".dark_blue
|
130
|
+
|
131
|
+
if ARGV[0] == "-h" || ARGV[0] == "help" || ARGV[0] == "h"
|
132
|
+
|
133
|
+
puts USAGE
|
134
|
+
|
135
|
+
elsif ARGV[0] == "daily" || ARGV[0] == "weekly"
|
136
|
+
#compute alltime stats for daily or weekly reports
|
137
|
+
|
138
|
+
dir_filter = ARGV[0] == "daily" ? "D" : "W"
|
139
|
+
|
140
|
+
alltime_proceeds_per_currency = {} #currency is the key, value is the proceeds
|
141
|
+
alltime_renewables = 0
|
142
|
+
alltime_apps = {}
|
143
|
+
alltime_payed_units = 0
|
144
|
+
alltime_inapp_units = 0
|
145
|
+
alltime_free_units = 0
|
146
|
+
alltime_updated_units = 0
|
147
|
+
reports = Dir["S_#{dir_filter}_*.txt"].uniq.compact
|
148
|
+
|
149
|
+
if reports.empty?
|
150
|
+
|
151
|
+
puts "\nPlease download reports first.".red
|
152
|
+
puts "sale get:#{ARGV[0].split(':').last}\n".green
|
153
|
+
|
154
|
+
else
|
155
|
+
|
156
|
+
first_date = reports[0].split('_').last.split('.').first
|
157
|
+
reports.each do |alltime_filename|
|
158
|
+
|
159
|
+
#puts "Processing #{alltime_filename}".green
|
160
|
+
|
161
|
+
#get the date from the filename
|
162
|
+
date = alltime_filename.split('_').last.split('.').first #filename example: S_D_80076793_20120706.txt
|
163
|
+
|
164
|
+
report_data = File.open(alltime_filename, "rb").read
|
165
|
+
|
166
|
+
report = SalesReportParser.parse(report_data)
|
167
|
+
#puts report.class
|
168
|
+
if report #report parsed
|
169
|
+
apps = {}
|
170
|
+
total_payed_units = 0
|
171
|
+
total_inapp_units = 0
|
172
|
+
total_free_units = 0
|
173
|
+
total_updated_units = 0
|
174
|
+
report.each do |item| #report is a hash
|
175
|
+
if item
|
176
|
+
sku = item[:sku] #group data by app sku
|
177
|
+
if apps.has_key? sku #app is already cached
|
178
|
+
app = apps[sku]
|
179
|
+
else #initially insert app
|
180
|
+
app = {:sku=>sku, :title=>item[:title], :sold_units=>0, :updated_units=>0}
|
181
|
+
apps[sku] = app
|
182
|
+
end
|
183
|
+
#ensure currency sum
|
184
|
+
alltime_proceeds_per_currency[item[:currency_of_proceeds]] = 0.0 unless alltime_proceeds_per_currency[item[:currency_of_proceeds]]
|
185
|
+
|
186
|
+
#count units
|
187
|
+
if SALE_IDENTS.include? item[:product_type_id] #count sales
|
188
|
+
app[:sold_units] += item[:units]
|
189
|
+
if item[:customer_price]==0 #a free app
|
190
|
+
total_free_units += item[:units]
|
191
|
+
else
|
192
|
+
total_payed_units += item[:units]
|
193
|
+
alltime_proceeds_per_currency[item[:currency_of_proceeds]] += item[:developer_proceeds]
|
194
|
+
end
|
195
|
+
elsif INAPP_SALE_IDENTS.include? item[:product_type_id]
|
196
|
+
app[:sold_units] += item[:units]
|
197
|
+
total_inapp_units += item[:units]
|
198
|
+
alltime_proceeds_per_currency[item[:currency_of_proceeds]] += item[:developer_proceeds]
|
199
|
+
if item[:product_type_id] == "IAY" #InAppPurchase
|
200
|
+
alltime_renewables += item[:units]
|
201
|
+
end
|
202
|
+
elsif UPDATE_IDENTS.include? item[:product_type_id] #count updates
|
203
|
+
app[:updated_units] += item[:units]
|
204
|
+
total_updated_units += item[:units]
|
205
|
+
end
|
206
|
+
else # only if item
|
207
|
+
puts "null report".red
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
#add to the alltime stats
|
212
|
+
alltime_payed_units += total_payed_units
|
213
|
+
alltime_inapp_units += total_inapp_units
|
214
|
+
alltime_free_units += total_free_units
|
215
|
+
alltime_updated_units += total_updated_units
|
216
|
+
|
217
|
+
apps.each do |alltime_sku, apps_app|
|
218
|
+
#select the app
|
219
|
+
if alltime_apps.has_key? alltime_sku
|
220
|
+
#already cached
|
221
|
+
alltime_app = alltime_apps[alltime_sku]
|
222
|
+
else
|
223
|
+
#insert for the first time
|
224
|
+
alltime_app = {:sku=>alltime_sku, :title=>apps_app[:title], :sold_units=>0, :updated_units=>0}
|
225
|
+
alltime_apps[alltime_sku] = alltime_app
|
226
|
+
end
|
227
|
+
#add stats
|
228
|
+
alltime_app[:sold_units] += apps_app[:sold_units]
|
229
|
+
alltime_app[:updated_units] += apps_app[:updated_units]
|
230
|
+
end
|
231
|
+
|
232
|
+
=begin
|
233
|
+
#report for date
|
234
|
+
puts "\n\n______________________________________________________________".blue
|
235
|
+
puts "Report for #{date}"
|
236
|
+
puts "\n" + "Product".ljust(40).blue + ": " +"Downloads".green + " / " + "Updates".green
|
237
|
+
puts "______________________________________________________________".yellow
|
238
|
+
apps.each do |app_sku,apps_app|
|
239
|
+
puts "#{apps_app[:title].ljust(40).blue}: #{apps_app[:sold_units].to_s.ljust(10).green} / #{apps_app[:updated_units].to_s.rjust(7).dark_green}"
|
240
|
+
end
|
241
|
+
puts "______________________________________________________________".yellow
|
242
|
+
puts "#{'InApp Purchases'.ljust(40).green}: #{total_inapp_units}"
|
243
|
+
puts "#{'Payed Downloads'.ljust(40).green}: #{total_payed_units}"
|
244
|
+
puts "#{'Free Downloads'.ljust(40).dark_green}: #{total_free_units}"
|
245
|
+
puts "#{'Updates'.ljust(40).dark_green}: #{total_updated_units}"
|
246
|
+
puts "______________________________________________________________".blue
|
247
|
+
puts "\n\n"
|
248
|
+
=end
|
249
|
+
|
250
|
+
else
|
251
|
+
puts "null report parsed".red
|
252
|
+
end #if report parsed
|
253
|
+
|
254
|
+
end #reports.each
|
255
|
+
|
256
|
+
#report alltime
|
257
|
+
puts "\n\n______________________________________________________________".blue
|
258
|
+
from = Date.strptime first_date, '%Y%m%d'
|
259
|
+
age = Date.today - from
|
260
|
+
formatted_from = from.strftime("%b %d %Y")
|
261
|
+
puts "Report for #{ARGV[0]}, from #{formatted_from}, #{age.to_i} days"
|
262
|
+
puts "\n" + "Product".ljust(40).blue + ": " +"Downloads".green + " / " + "Updates".green
|
263
|
+
puts "______________________________________________________________".yellow
|
264
|
+
alltime_apps.each do |app_sku, aapp|
|
265
|
+
puts "#{aapp[:title].ljust(40).blue}: #{aapp[:sold_units].to_s.ljust(10).green} / #{aapp[:updated_units].to_s.rjust(7).dark_green}"
|
266
|
+
end
|
267
|
+
puts "______________________________________________________________".yellow
|
268
|
+
puts "#{'InApp Purchases'.ljust(40).green}: #{alltime_inapp_units}" + ( alltime_renewables > 0.0 ? " / #{alltime_renewables} Auto-Renewed" : "")
|
269
|
+
puts "#{'Payed Downloads'.ljust(40).green}: #{alltime_payed_units}"
|
270
|
+
puts "#{'Free Downloads'.ljust(40).dark_green}: #{alltime_free_units}"
|
271
|
+
puts "#{'Updates'.ljust(40).dark_green}: #{alltime_updated_units}"
|
272
|
+
puts "\n#{'Proceeds'.red}:\n\n"
|
273
|
+
total_eurs = 0.0
|
274
|
+
alltime_proceeds_per_currency.each do |proceed_key, proceed|
|
275
|
+
formatted_sum = proceed > 0.0 ? "#{proceed}".green : "#{proceed}".red
|
276
|
+
if proceed > 0.0
|
277
|
+
if proceed_key == "EUR"
|
278
|
+
total_eurs += proceed
|
279
|
+
puts "#{proceed_key} : #{formatted_sum}"
|
280
|
+
else
|
281
|
+
#convert using google
|
282
|
+
data = open("http://www.google.com/ig/calculator?q=#{proceed}#{proceed_key}=?EUR").read
|
283
|
+
#fix broken json
|
284
|
+
data.gsub!(/lhs:/, '"lhs":')
|
285
|
+
data.gsub!(/rhs:/, '"rhs":')
|
286
|
+
data.gsub!(/error:/, '"error":')
|
287
|
+
data.gsub!(/icc:/, '"icc":')
|
288
|
+
data.gsub!(Regexp.new("(\\\\x..|\\\\240)"), '')
|
289
|
+
#puts data
|
290
|
+
converted = JSON.parse data
|
291
|
+
converted_proceed = converted["rhs"].split(' ').first.to_f
|
292
|
+
total_eurs += converted_proceed
|
293
|
+
puts "#{proceed_key} : #{formatted_sum} / #{converted['rhs']}"
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
puts "\n#{'Total'.green}: #{total_eurs} Euros"
|
298
|
+
puts "______________________________________________________________".blue
|
299
|
+
puts "\n\n"
|
300
|
+
|
301
|
+
end #else reports.empty?
|
302
|
+
|
303
|
+
elsif ARGV[0] == "get:daily"
|
304
|
+
|
305
|
+
# Daily reports are available only for past 14 days, please enter a date within past 14 days.
|
306
|
+
|
307
|
+
first_date = Date.today
|
308
|
+
|
309
|
+
(1..14).each do |i|
|
310
|
+
|
311
|
+
date = (first_date - i).to_s.gsub('-', '')
|
312
|
+
puts "\nGetting Daily Sales Report for #{date}\n"
|
313
|
+
|
314
|
+
filename = "S_D_#{vendorId}_#{date}.txt"
|
315
|
+
unless File.exists? filename #download unless there already is a file
|
316
|
+
#call the java program and fetch the file
|
317
|
+
e = `java -cp #{classpath} Autoingestion #{username} #{password} #{vendorId} Sales Daily Summary #{date}`
|
318
|
+
report_file = e.split("\n").first
|
319
|
+
if File.exists? report_file
|
320
|
+
f = `gzip -df #{report_file}`
|
321
|
+
else
|
322
|
+
puts "#{e}\n".red
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
end # 91.each
|
327
|
+
|
328
|
+
elsif ARGV[0] == "get:weekly"
|
329
|
+
|
330
|
+
# Weekly reports are available only for past 13 weeks, please enter a weekend date within past 13 weeks.
|
331
|
+
|
332
|
+
first_date = Date.today
|
333
|
+
(1..13).each do |i| #13 weeks
|
334
|
+
date = (first_date - i*7)
|
335
|
+
day_increment = (0 - date.cwday) % 7
|
336
|
+
day_increment = 7 if day_increment == 0
|
337
|
+
next_sunday = date + day_increment
|
338
|
+
formatted_date = next_sunday.to_s.gsub('-', '')
|
339
|
+
puts "\nGetting Weekly Sales Report for #{formatted_date}\n"
|
340
|
+
filename = "S_W_#{vendorId}_#{formatted_date}.txt"
|
341
|
+
unless File.exists? filename #download unless there already is a file
|
342
|
+
#call the java program and fetch the file
|
343
|
+
e = `java -cp #{classpath} Autoingestion #{username} #{password} #{vendorId} Sales Weekly Summary #{formatted_date}`
|
344
|
+
report_file = e.split("\n").first
|
345
|
+
if File.exists? report_file
|
346
|
+
f = `gzip -df #{report_file}`
|
347
|
+
else
|
348
|
+
puts "#{e}\n".red
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end # 13.each
|
352
|
+
|
353
|
+
else
|
354
|
+
# no argument or date in format YYYYMMDD
|
355
|
+
#get sales report for date
|
356
|
+
|
357
|
+
@date = ARGV[0]
|
358
|
+
date = (Date.today - 1).to_s.gsub('-', '')
|
359
|
+
date = @date if ARGV[0]
|
360
|
+
puts "\nDaily Sales Report for #{date}\n"
|
361
|
+
|
362
|
+
filename = "S_D_#{vendorId}_#{date}.txt"
|
363
|
+
unless File.exists? filename #download unless there already is a file
|
364
|
+
#call the java program and fetch the file
|
365
|
+
e = `java -cp #{classpath} Autoingestion #{username} #{password} #{vendorId} Sales Daily Summary #{date}`
|
366
|
+
report_file = e.split("\n").first
|
367
|
+
if File.exists? report_file
|
368
|
+
f = `gzip -df #{report_file}`
|
369
|
+
else
|
370
|
+
puts "#{e}\n".red
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
if File.exists? filename #only if there is data
|
375
|
+
#calculate totals
|
376
|
+
report_data = File.open(filename, "rb").read
|
377
|
+
report = SalesReportParser.parse(report_data)
|
378
|
+
apps = {}
|
379
|
+
total_payed_units = 0
|
380
|
+
total_inapp_units = 0
|
381
|
+
total_free_units = 0
|
382
|
+
total_updated_units = 0
|
383
|
+
report.each do |item|
|
384
|
+
sku = item[:sku] #group data by app sku
|
385
|
+
if apps.has_key? sku #app is already cached
|
386
|
+
app = apps[sku]
|
387
|
+
else #initially insert app
|
388
|
+
app = {:sku=>sku, :title=>item[:title], :sold_units=>0, :updated_units=>0}
|
389
|
+
apps[sku] = app
|
390
|
+
end
|
391
|
+
#count units
|
392
|
+
if SALE_IDENTS.include? item[:product_type_id] #count sales
|
393
|
+
app[:sold_units] += item[:units]
|
394
|
+
if item[:customer_price]==0 #a free app
|
395
|
+
total_free_units += item[:units]
|
396
|
+
else
|
397
|
+
total_payed_units += item[:units]
|
398
|
+
end
|
399
|
+
elsif INAPP_SALE_IDENTS.include? item[:product_type_id]
|
400
|
+
app[:sold_units] += item[:units]
|
401
|
+
total_inapp_units += item[:units]
|
402
|
+
elsif UPDATE_IDENTS.include? item[:product_type_id] #count updates
|
403
|
+
app[:updated_units] += item[:units]
|
404
|
+
total_updated_units += item[:units]
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
#report
|
409
|
+
puts "\n" + "Product".ljust(40).blue + ": " +"Downloads".green + " / " + "Updates".green
|
410
|
+
puts "____________________________________________________________".yellow
|
411
|
+
apps.each do |app_sku, app|
|
412
|
+
puts "#{app[:title].ljust(40).blue}: #{app[:sold_units].to_s.ljust(10).green} / #{app[:updated_units].to_s.rjust(7).dark_green}"
|
413
|
+
end
|
414
|
+
puts "____________________________________________________________".yellow
|
415
|
+
puts "#{'InApp Purchases'.ljust(40).green}: #{total_inapp_units}"
|
416
|
+
puts "#{'Payed Downloads'.ljust(40).green}: #{total_payed_units}"
|
417
|
+
puts "#{'Free Downloads'.ljust(40).dark_green}: #{total_free_units}"
|
418
|
+
puts "#{'Updates'.ljust(40).dark_green}: #{total_updated_units}"
|
419
|
+
puts "\n"
|
420
|
+
end
|
421
|
+
end
|
data/sales.yml
ADDED
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sales
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mihael
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-07-07 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: iTunes Connect Command Line Autoingestion Script. Computes and presents totals. Uses Autoingestion.class for report downloading.
|
17
|
+
email: kitschmaster@gmail.com
|
18
|
+
executables:
|
19
|
+
- sale
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.md
|
24
|
+
files:
|
25
|
+
- Autoingestion.class
|
26
|
+
- README.md
|
27
|
+
- Rakefile
|
28
|
+
- VERSION.yml
|
29
|
+
- bin/sale
|
30
|
+
- sales.yml
|
31
|
+
homepage: http://github.com/mihael/sales
|
32
|
+
licenses: []
|
33
|
+
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
requirements: []
|
52
|
+
|
53
|
+
rubyforge_project: Sales
|
54
|
+
rubygems_version: 1.8.17
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: iTunes Connect Command Line Autoingestion Script.
|
58
|
+
test_files: []
|
59
|
+
|