sales 0.0.2 → 0.0.3
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/README.md +26 -1
- data/VERSION.yml +1 -1
- data/bin/sale +131 -123
- data/sales.yml +2 -0
- metadata +2 -2
data/README.md
CHANGED
@@ -18,7 +18,32 @@ open _sales.yml_ and fill in Your iTunes Connect credentials:
|
|
18
18
|
:username: kitschmaster@gmail.com #iTunes connect username
|
19
19
|
:password: yourpassword #iTunes Connect password
|
20
20
|
:vendorId: 80076733 #iTunes Connect -> Sales and Trends, find the vendorId on the header of the table next to the company name
|
21
|
+
:convertTo: EUR #currency to convert the total proceeds into, EUR, USD, MXN, GBP, CHF...
|
22
|
+
:beVerbose: YES #verbose output
|
21
23
|
|
22
24
|
with the credentials in place run _sale_ again, it should now download the latest daily report and present it.
|
23
25
|
|
24
|
-
run `sale help` to get a list of all possible commands.
|
26
|
+
run `sale help` to get a list of all possible commands.
|
27
|
+
|
28
|
+
dependencies
|
29
|
+
============
|
30
|
+
|
31
|
+
OSX + java runtime + ruby1.9.2
|
32
|
+
|
33
|
+
example output
|
34
|
+
==============
|
35
|
+
|
36
|
+

|
37
|
+
|
38
|
+
idea
|
39
|
+
====
|
40
|
+
|
41
|
+
ii could not afford a really nice payed Mac App or such to see my poor iPhone Developer sales stats.
|
42
|
+
so ii went into the esoteric garage and coded this little script for my self.
|
43
|
+
|
44
|
+
ii am Your Headless Standup Programmer, ii stand while ii code, and ii can't see my own head.
|
45
|
+
|
46
|
+
wish
|
47
|
+
====
|
48
|
+
|
49
|
+
ii wish ii would have a nice remote iPhone development or ruby related job, well payed and about something ii love.
|
data/VERSION.yml
CHANGED
data/bin/sale
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
# Sales
|
3
|
+
# Sales, still wet, not yet dry
|
4
4
|
|
5
5
|
#make nice colors for terminal strings
|
6
6
|
class String
|
@@ -36,12 +36,14 @@ USAGE = <<-EGASU
|
|
36
36
|
|
37
37
|
EGASU
|
38
38
|
|
39
|
+
#require all dependent gems
|
39
40
|
require 'rubygems'
|
40
41
|
require 'open-uri'
|
41
42
|
require 'json'
|
42
43
|
require 'date'
|
43
44
|
require 'yaml'
|
44
45
|
|
46
|
+
#load the version file
|
45
47
|
V = YAML.load_file(File.join(File.dirname(__FILE__), %w[.. VERSION.yml]))
|
46
48
|
VERSION = "#{V[:major]}.#{V[:minor]}.#{V[:patch]}"
|
47
49
|
|
@@ -50,9 +52,11 @@ example_sales_yml_file = File.join(File.dirname(__FILE__), %w[.. sales.yml])
|
|
50
52
|
dest_sales_yml_file = "sales.yml"
|
51
53
|
if File.exists? dest_sales_yml_file
|
52
54
|
S = YAML.load_file("sales.yml")
|
53
|
-
username = S[:username]
|
54
|
-
password = S[:password]
|
55
|
-
vendorId = S[:vendorId]
|
55
|
+
@username = S[:username]
|
56
|
+
@password = S[:password]
|
57
|
+
@vendorId = S[:vendorId]
|
58
|
+
@convertTo = S[:convertTo]
|
59
|
+
@beVerbose = S[:beVerbose]
|
56
60
|
else
|
57
61
|
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
62
|
`cp #{example_sales_yml_file} sales.yml; ls -al` unless File.exists? dest_sales_yml_file
|
@@ -84,86 +88,71 @@ SALE_IDENTS = ["1", "1F", "1T", "F1"]
|
|
84
88
|
INAPP_SALE_IDENTS = ["IA1", "IA9", "IAY", "FI1"]
|
85
89
|
UPDATE_IDENTS = ["7", "7F", "7T", "F7"]
|
86
90
|
|
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
|
-
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?
|
91
|
+
# Parse sales report
|
92
|
+
# the parser was copied and tweaked from github, https://github.com/siuying/itunes-auto-ingestion/blob/master/lib/itunes_ingestion/sales_report_parser.rb, thanks
|
93
|
+
# the class was thrown away though, a simple method is so much cleaner in a simple program like this
|
94
|
+
#
|
95
|
+
# report - text based report form itunesconnect
|
96
|
+
#
|
97
|
+
# Returns array of hash, each hash contains one line of sales report
|
98
|
+
def parse(report)
|
99
|
+
lines = report.split("\n")
|
100
|
+
#puts "lines: #{lines}"
|
101
|
+
header = lines.shift # remove first line
|
102
|
+
lines.collect do |line|
|
103
|
+
#puts "line: #{line}"
|
104
|
+
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")
|
105
|
+
p = { :provider => provider.strip,
|
106
|
+
:country => country.strip,
|
107
|
+
:sku => sku.strip,
|
108
|
+
:developer => developer.strip,
|
109
|
+
:title => title.strip,
|
110
|
+
:version => version.strip,
|
111
|
+
:product_type_id => product_type_id.strip,
|
112
|
+
:units => units.to_i,
|
113
|
+
:developer_proceeds => developer_proceeds.to_f,
|
114
|
+
:begin_date => Date.strptime(begin_date.strip, '%m/%d/%Y'),
|
115
|
+
:end_date => Date.strptime(end_date.strip, '%m/%d/%Y'),
|
116
|
+
:currency => currency.strip,
|
117
|
+
:country_code => country_code.strip,
|
118
|
+
:currency_of_proceeds => currency_of_proceeds.strip,
|
119
|
+
:apple_id => apple_id.to_i,
|
120
|
+
:customer_price => customer_price.to_f,
|
121
|
+
:promo_code => promo_code.strip,
|
122
|
+
:parent_id => parent_id.strip,
|
123
|
+
:subscription => subscription.strip,
|
124
|
+
:period => period
|
125
|
+
}
|
126
|
+
puts "parsing failed".red if p==nil
|
127
|
+
p
|
128
|
+
end #lines collect
|
129
|
+
end #parse
|
130
|
+
|
131
|
+
# Computes and presents reports
|
132
|
+
#
|
133
|
+
# reports - array of report files to compute
|
134
|
+
#
|
135
|
+
#
|
136
|
+
def compute_and_present(reports)
|
137
|
+
alltime_proceeds_per_currency = {} #currency is the key, value is the proceeds
|
138
|
+
alltime_renewables = 0
|
139
|
+
alltime_apps = {}
|
140
|
+
alltime_payed_units = 0
|
141
|
+
alltime_inapp_units = 0
|
142
|
+
alltime_free_units = 0
|
143
|
+
alltime_updated_units = 0
|
150
144
|
|
151
|
-
puts "\nPlease download reports first.".red
|
152
|
-
puts "sale get:#{ARGV[0].split(':').last}\n".green
|
153
|
-
|
154
|
-
else
|
155
|
-
|
156
145
|
first_date = reports[0].split('_').last.split('.').first
|
157
146
|
reports.each do |alltime_filename|
|
158
147
|
|
159
|
-
|
148
|
+
puts "Processing #{alltime_filename}".green if @beVerbose
|
160
149
|
|
161
150
|
#get the date from the filename
|
162
151
|
date = alltime_filename.split('_').last.split('.').first #filename example: S_D_80076793_20120706.txt
|
163
152
|
|
164
153
|
report_data = File.open(alltime_filename, "rb").read
|
165
154
|
|
166
|
-
report =
|
155
|
+
report = parse(report_data)
|
167
156
|
#puts report.class
|
168
157
|
if report #report parsed
|
169
158
|
apps = {}
|
@@ -229,23 +218,23 @@ elsif ARGV[0] == "daily" || ARGV[0] == "weekly"
|
|
229
218
|
alltime_app[:updated_units] += apps_app[:updated_units]
|
230
219
|
end
|
231
220
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
221
|
+
if @beVerbose && reports.size>1
|
222
|
+
#report for date
|
223
|
+
puts "\n\n______________________________________________________________".blue
|
224
|
+
puts "Report for #{date}"
|
225
|
+
puts "\n" + "Product".ljust(40).blue + ": " +"Downloads".green + " / " + "Updates".green
|
226
|
+
puts "______________________________________________________________".yellow
|
227
|
+
apps.each do |app_sku,apps_app|
|
228
|
+
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}"
|
229
|
+
end
|
230
|
+
puts "______________________________________________________________".yellow
|
231
|
+
puts "#{'InApp Purchases'.ljust(40).green}: #{total_inapp_units}"
|
232
|
+
puts "#{'Payed Downloads'.ljust(40).green}: #{total_payed_units}"
|
233
|
+
puts "#{'Free Downloads'.ljust(40).dark_green}: #{total_free_units}"
|
234
|
+
puts "#{'Updates'.ljust(40).dark_green}: #{total_updated_units}"
|
235
|
+
puts "______________________________________________________________".blue
|
236
|
+
puts "\n\n"
|
237
|
+
end #if @beVerbose
|
249
238
|
|
250
239
|
else
|
251
240
|
puts "null report parsed".red
|
@@ -258,7 +247,7 @@ elsif ARGV[0] == "daily" || ARGV[0] == "weekly"
|
|
258
247
|
from = Date.strptime first_date, '%Y%m%d'
|
259
248
|
age = Date.today - from
|
260
249
|
formatted_from = from.strftime("%b %d %Y")
|
261
|
-
puts "Report
|
250
|
+
puts "Report" + (ARGV[0]? " #{ARGV[0]}":" daily") + ", from #{formatted_from}, #{age.to_i} days"
|
262
251
|
puts "\n" + "Product".ljust(40).blue + ": " +"Downloads".green + " / " + "Updates".green
|
263
252
|
puts "______________________________________________________________".yellow
|
264
253
|
alltime_apps.each do |app_sku, aapp|
|
@@ -270,16 +259,16 @@ elsif ARGV[0] == "daily" || ARGV[0] == "weekly"
|
|
270
259
|
puts "#{'Free Downloads'.ljust(40).dark_green}: #{alltime_free_units}"
|
271
260
|
puts "#{'Updates'.ljust(40).dark_green}: #{alltime_updated_units}"
|
272
261
|
puts "\n#{'Proceeds'.red}:\n\n"
|
273
|
-
|
262
|
+
total_proceeds = 0.0
|
274
263
|
alltime_proceeds_per_currency.each do |proceed_key, proceed|
|
275
264
|
formatted_sum = proceed > 0.0 ? "#{proceed}".green : "#{proceed}".red
|
276
265
|
if proceed > 0.0
|
277
|
-
if proceed_key ==
|
278
|
-
|
266
|
+
if proceed_key == @convertTo
|
267
|
+
total_proceeds += proceed
|
279
268
|
puts "#{proceed_key} : #{formatted_sum}"
|
280
269
|
else
|
281
270
|
#convert using google
|
282
|
-
data = open("http://www.google.com/ig/calculator?q=#{proceed}#{proceed_key}
|
271
|
+
data = open("http://www.google.com/ig/calculator?q=#{proceed}#{proceed_key}=?#{@convertTo}").read
|
283
272
|
#fix broken json
|
284
273
|
data.gsub!(/lhs:/, '"lhs":')
|
285
274
|
data.gsub!(/rhs:/, '"rhs":')
|
@@ -289,32 +278,51 @@ elsif ARGV[0] == "daily" || ARGV[0] == "weekly"
|
|
289
278
|
#puts data
|
290
279
|
converted = JSON.parse data
|
291
280
|
converted_proceed = converted["rhs"].split(' ').first.to_f
|
292
|
-
|
281
|
+
total_proceeds += converted_proceed
|
293
282
|
puts "#{proceed_key} : #{formatted_sum} / #{converted['rhs']}"
|
294
283
|
end
|
295
284
|
end
|
296
285
|
end
|
297
|
-
puts "\n#{'Total'.green}: #{
|
286
|
+
puts "\n#{'Total'.green}: #{total_proceeds} #{@convertTo}"
|
298
287
|
puts "______________________________________________________________".blue
|
299
|
-
puts "\n\n"
|
288
|
+
puts "\n\n"
|
289
|
+
end
|
300
290
|
|
301
|
-
end #else reports.empty?
|
302
291
|
|
292
|
+
#begin to show visible light of this program by displaying its name, version and creator
|
293
|
+
puts "\nSales v#{VERSION}".red + " created by Your Headless Standup Programmer http://kitschmaster.com".dark_blue if @beVerbose
|
294
|
+
|
295
|
+
if ARGV[0] == "-h" || ARGV[0] == "help" || ARGV[0] == "h"
|
296
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
297
|
+
#show help
|
298
|
+
puts USAGE
|
299
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
300
|
+
elsif ARGV[0] == "daily" || ARGV[0] == "weekly"
|
301
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
302
|
+
#compute alltime stats for daily or weekly reports
|
303
|
+
#collect the report files
|
304
|
+
dir_filter = ARGV[0] == "daily" ? "D" : "W" #the daily or the weekly files
|
305
|
+
reports = Dir["S_#{dir_filter}_*.txt"].uniq.compact
|
306
|
+
if reports.empty?
|
307
|
+
#no reports
|
308
|
+
puts "\nPlease download reports first.".red
|
309
|
+
puts "sale get:#{ARGV[0].split(':').last}\n".green
|
310
|
+
else
|
311
|
+
#compute and present the collected reports
|
312
|
+
compute_and_present(reports)
|
313
|
+
end
|
314
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
303
315
|
elsif ARGV[0] == "get:daily"
|
304
|
-
|
316
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
305
317
|
# Daily reports are available only for past 14 days, please enter a date within past 14 days.
|
306
|
-
|
307
|
-
first_date = Date.today
|
308
|
-
|
318
|
+
first_date = Date.today
|
309
319
|
(1..14).each do |i|
|
310
|
-
|
311
320
|
date = (first_date - i).to_s.gsub('-', '')
|
312
321
|
puts "\nGetting Daily Sales Report for #{date}\n"
|
313
|
-
|
314
|
-
filename = "S_D_#{vendorId}_#{date}.txt"
|
322
|
+
filename = "S_D_#{@vendorId}_#{date}.txt"
|
315
323
|
unless File.exists? filename #download unless there already is a file
|
316
324
|
#call the java program and fetch the file
|
317
|
-
e = `java -cp #{classpath} Autoingestion #{username} #{password} #{vendorId} Sales Daily Summary #{date}`
|
325
|
+
e = `java -cp #{classpath} Autoingestion #{@username} #{@password} #{@vendorId} Sales Daily Summary #{date}`
|
318
326
|
report_file = e.split("\n").first
|
319
327
|
if File.exists? report_file
|
320
328
|
f = `gzip -df #{report_file}`
|
@@ -322,13 +330,11 @@ elsif ARGV[0] == "get:daily"
|
|
322
330
|
puts "#{e}\n".red
|
323
331
|
end
|
324
332
|
end
|
325
|
-
|
326
|
-
|
327
|
-
|
333
|
+
end # 1..14.each
|
334
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
328
335
|
elsif ARGV[0] == "get:weekly"
|
329
|
-
|
336
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
330
337
|
# Weekly reports are available only for past 13 weeks, please enter a weekend date within past 13 weeks.
|
331
|
-
|
332
338
|
first_date = Date.today
|
333
339
|
(1..13).each do |i| #13 weeks
|
334
340
|
date = (first_date - i*7)
|
@@ -337,10 +343,10 @@ elsif ARGV[0] == "get:weekly"
|
|
337
343
|
next_sunday = date + day_increment
|
338
344
|
formatted_date = next_sunday.to_s.gsub('-', '')
|
339
345
|
puts "\nGetting Weekly Sales Report for #{formatted_date}\n"
|
340
|
-
filename = "S_W_#{vendorId}_#{formatted_date}.txt"
|
346
|
+
filename = "S_W_#{@vendorId}_#{formatted_date}.txt"
|
341
347
|
unless File.exists? filename #download unless there already is a file
|
342
348
|
#call the java program and fetch the file
|
343
|
-
e = `java -cp #{classpath} Autoingestion #{username} #{password} #{vendorId} Sales Weekly Summary #{formatted_date}`
|
349
|
+
e = `java -cp #{classpath} Autoingestion #{@username} #{@password} #{@vendorId} Sales Weekly Summary #{formatted_date}`
|
344
350
|
report_file = e.split("\n").first
|
345
351
|
if File.exists? report_file
|
346
352
|
f = `gzip -df #{report_file}`
|
@@ -348,21 +354,20 @@ elsif ARGV[0] == "get:weekly"
|
|
348
354
|
puts "#{e}\n".red
|
349
355
|
end
|
350
356
|
end
|
351
|
-
end # 13.each
|
352
|
-
|
357
|
+
end # 1..13.each
|
358
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
353
359
|
else
|
360
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
354
361
|
# no argument or date in format YYYYMMDD
|
355
362
|
#get sales report for date
|
356
|
-
|
357
363
|
@date = ARGV[0]
|
358
364
|
date = (Date.today - 1).to_s.gsub('-', '')
|
359
365
|
date = @date if ARGV[0]
|
360
366
|
puts "\nDaily Sales Report for #{date}\n"
|
361
|
-
|
362
|
-
filename = "S_D_#{vendorId}_#{date}.txt"
|
367
|
+
filename = "S_D_#{@vendorId}_#{date}.txt"
|
363
368
|
unless File.exists? filename #download unless there already is a file
|
364
369
|
#call the java program and fetch the file
|
365
|
-
e = `java -cp #{classpath} Autoingestion #{username} #{password} #{vendorId} Sales Daily Summary #{date}`
|
370
|
+
e = `java -cp #{classpath} Autoingestion #{@username} #{@password} #{@vendorId} Sales Daily Summary #{date}`
|
366
371
|
report_file = e.split("\n").first
|
367
372
|
if File.exists? report_file
|
368
373
|
f = `gzip -df #{report_file}`
|
@@ -370,11 +375,12 @@ else
|
|
370
375
|
puts "#{e}\n".red
|
371
376
|
end
|
372
377
|
end
|
373
|
-
|
378
|
+
compute_and_present([filename])
|
379
|
+
=begin
|
374
380
|
if File.exists? filename #only if there is data
|
375
381
|
#calculate totals
|
376
382
|
report_data = File.open(filename, "rb").read
|
377
|
-
report =
|
383
|
+
report = parse(report_data)
|
378
384
|
apps = {}
|
379
385
|
total_payed_units = 0
|
380
386
|
total_inapp_units = 0
|
@@ -418,4 +424,6 @@ else
|
|
418
424
|
puts "#{'Updates'.ljust(40).dark_green}: #{total_updated_units}"
|
419
425
|
puts "\n"
|
420
426
|
end
|
427
|
+
=end
|
428
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
421
429
|
end
|
data/sales.yml
CHANGED
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: sales
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Mihael
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2012-07-
|
13
|
+
date: 2012-07-08 00:00:00 Z
|
14
14
|
dependencies: []
|
15
15
|
|
16
16
|
description: iTunes Connect Command Line Autoingestion Script. Computes and presents totals. Uses Autoingestion.class for report downloading.
|