codes 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +19 -1
- data/bin/codes +34 -2
- data/lib/codes/codes_runner.rb +5 -2
- data/lib/codes/itunes_connect.rb +89 -26
- data/lib/codes/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34b56b7948df3498826c2deed30e0b559ee3b0ae
|
4
|
+
data.tar.gz: 8c1e4e68aaaae4125efefcc4a7e57e88927ec011
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa064ec5e1229d765f377227dd26f138e89e8fdcf8a48ebae235dbec31a4eb19e0ae65a9034097dfe9d347ffd56c4cd5793355e0b35831c76257cb75369e3f8b
|
7
|
+
data.tar.gz: 45e3a769ce73b7ac8fa38e18fa2853d39fd77b10fe42102debb1790e76b12bbca30ee31285085e58634deffc45613906565e565fe68226b09fe8862a1f9d36ff
|
data/README.md
CHANGED
@@ -70,7 +70,7 @@ Make sure, you have the latest version of the Xcode command line tools installed
|
|
70
70
|
|
71
71
|
# Usage
|
72
72
|
|
73
|
-
codes [num] [-a app_identifier] [-u user_name] [-i app_id] [-o output_file]
|
73
|
+
codes [num] [-a app_identifier] [-u user_name] [-i app_id] [-o output_file] [-c country] [-X] [-f format]
|
74
74
|
|
75
75
|
All parameters are optional.
|
76
76
|
|
@@ -84,6 +84,24 @@ Will generate 3 promo codes for the the App with the Bundle Identifier `com.exam
|
|
84
84
|
|
85
85
|
If you don't pass any paramaters, `codes` will generate a single promo code and print it on the command line.
|
86
86
|
|
87
|
+
## Display extra information
|
88
|
+
|
89
|
+
Use the ```--format FORMAT``` argument to control the displayed information. The ```--verbose``` argument displays all information and maps to
|
90
|
+
|
91
|
+
--format '%c,%d,%p,%i,%b,%n,%u'
|
92
|
+
|
93
|
+
E.g.
|
94
|
+
|
95
|
+
codes --verbose
|
96
|
+
|
97
|
+
will display
|
98
|
+
|
99
|
+
NRTFXP3XXXXX,20150520110716,ios,522069155,com.wewanttoknow.DragonBoxPlus,'DragonBox Algebra 5+',https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/redeemLandingPage?code=NRTFXP3XXXXX
|
100
|
+
|
101
|
+
For example, if you want a custom URL, you could use something like
|
102
|
+
|
103
|
+
codes --format '%c: http://yoursite.com/redeem/%c'
|
104
|
+
|
87
105
|
## Generate URLs
|
88
106
|
|
89
107
|
codes 5 --urls
|
data/bin/codes
CHANGED
@@ -12,6 +12,10 @@ HighLine.track_eof = false
|
|
12
12
|
class CodesApplication
|
13
13
|
include Commander::Methods
|
14
14
|
|
15
|
+
def download_verbose_format
|
16
|
+
'%c,%d,%p,%i,%b,%n,%u'
|
17
|
+
end
|
18
|
+
|
15
19
|
def run
|
16
20
|
program :version, Codes::VERSION
|
17
21
|
program :description, 'CLI for \'codes\' - Create promo codes for iOS Apps using the command line'
|
@@ -27,6 +31,8 @@ class CodesApplication
|
|
27
31
|
global_option '-a', '--app_identifier STRING', 'The bundle ID to download promo codes for'
|
28
32
|
global_option '-i', '--apple_id STRING', 'The App ID to download promo codes for'
|
29
33
|
global_option '-o', '--output_file STRING', 'The name of the file that should be written to disk. If the file exists, the new codes will be appended.'
|
34
|
+
global_option '-f', '--format STRING', 'If specified, modifies the format of the output to the specified format'
|
35
|
+
global_option '-X', '--verbose', "If specified, defines the default output. Similar to -f \'#{download_verbose_format}\'"
|
30
36
|
global_option '-x', '--urls', 'If specified, includes a full URL for each code that can be used to redeem that code.'
|
31
37
|
|
32
38
|
|
@@ -39,12 +45,24 @@ class CodesApplication
|
|
39
45
|
number_of_codes = count(args)
|
40
46
|
apple_id = apple_id(options)
|
41
47
|
app_identifier = bundle_id(options, apple_id)
|
42
|
-
|
48
|
+
format = download_format options
|
43
49
|
|
44
|
-
Codes::CodesRunner.
|
50
|
+
Codes::CodesRunner.download(number_of_codes: number_of_codes, app_identifier: app_identifier, apple_id: apple_id, output_file_path: options.output_file, format: format, country: options.country)
|
45
51
|
end
|
46
52
|
end
|
47
53
|
|
54
|
+
command :display do |c|
|
55
|
+
c.syntax = "codes display"
|
56
|
+
c.description = "Display remaining number of promo codes from iTunes Connect"
|
57
|
+
|
58
|
+
c.action do |args, options|
|
59
|
+
user = username options
|
60
|
+
apple_id = apple_id(options)
|
61
|
+
app_identifier = bundle_id(options, apple_id)
|
62
|
+
|
63
|
+
Codes::CodesRunner.display(app_identifier: app_identifier, apple_id: apple_id, output_file_path: options.output_file, country: options.country)
|
64
|
+
end
|
65
|
+
end
|
48
66
|
default_command :download
|
49
67
|
|
50
68
|
def count(args)
|
@@ -62,6 +80,20 @@ class CodesApplication
|
|
62
80
|
user
|
63
81
|
end
|
64
82
|
|
83
|
+
def download_format(options)
|
84
|
+
verbose = options.X || options.verbose
|
85
|
+
urls = options.x || options.urls
|
86
|
+
format = options.f || options.format
|
87
|
+
# backward compatibility...
|
88
|
+
if urls && !format
|
89
|
+
format = "%c - %u"
|
90
|
+
end
|
91
|
+
if verbose && !format
|
92
|
+
format = download_verbose_format
|
93
|
+
end
|
94
|
+
format
|
95
|
+
end
|
96
|
+
|
65
97
|
def bundle_id(options, apple_id)
|
66
98
|
app_identifier = options.app_identifier
|
67
99
|
app_identifier ||= ENV["CODES_APP_IDENTIFIER"]
|
data/lib/codes/codes_runner.rb
CHANGED
data/lib/codes/itunes_connect.rb
CHANGED
@@ -6,41 +6,38 @@ module Codes
|
|
6
6
|
PROMO_URL = "https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/wa/LCAppPage/viewPromoCodes?adamId=[[app_id]]&platform=[[platform]]"
|
7
7
|
CODE_URL = "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/redeemLandingPage?code=[[code]]"
|
8
8
|
|
9
|
-
def
|
9
|
+
def download(args)
|
10
10
|
number_of_codes = args[:number_of_codes]
|
11
|
-
country = args[:country]
|
12
11
|
|
13
12
|
code_or_codes = number_of_codes == 1 ? "code" : "codes"
|
14
13
|
Helper.log.info "Downloading #{number_of_codes} promo #{code_or_codes}..."
|
15
14
|
|
16
|
-
|
17
|
-
app_id = args[:apple_id]
|
18
|
-
app_id ||= (FastlaneCore::ItunesSearchApi.fetch_by_identifier(args[:app_identifier], country)['trackId'] rescue nil)
|
19
|
-
|
20
|
-
app_identifier = args[:app_identifier]
|
21
|
-
|
22
|
-
if app_id.to_i == 0 or app_identifier.to_s.length == 0
|
23
|
-
raise "Could not find app using the following information: #{args}. Maybe the app is not in the store. Pass the Apple ID of the app as well!".red
|
24
|
-
end
|
25
|
-
|
26
|
-
app = FastlaneCore::ItunesSearchApi.fetch(app_id, country)
|
27
|
-
platform = (app['kind'] == "mac-software" ? "osx" : "ios")
|
15
|
+
fetch_app_data args
|
28
16
|
|
29
17
|
# Use Pathname because it correctly handles the distinction between relative paths vs. absolute paths
|
30
18
|
output_file_path = Pathname.new(args[:output_file_path]) if args[:output_file_path]
|
31
|
-
output_file_path ||= Pathname.new(File.join(Dir.getwd, "#{app_identifier || app_id}_codes.txt"))
|
19
|
+
output_file_path ||= Pathname.new(File.join(Dir.getwd, "#{@app_identifier || @app_id}_codes.txt"))
|
32
20
|
raise "Insufficient permissions to write to output file".red if File.exists?(output_file_path) and not File.writable?(output_file_path)
|
33
|
-
visit PROMO_URL.gsub("[[app_id]]", app_id.to_s).gsub("[[platform]]", platform)
|
21
|
+
visit PROMO_URL.gsub("[[app_id]]", @app_id.to_s).gsub("[[platform]]", @platform)
|
34
22
|
|
35
23
|
begin
|
36
24
|
text_fields = wait_for_elements("input[type=text]")
|
37
25
|
rescue
|
38
|
-
raise "Could not open details page for app #{app_identifier}. Are you sure you are using the correct apple account and have access to this app?".red
|
26
|
+
raise "Could not open details page for app #{@app_identifier}. Are you sure you are using the correct apple account and have access to this app?".red
|
39
27
|
end
|
40
28
|
raise "There should only be a single text input field to specify the number of codes".red unless text_fields.count == 1
|
41
29
|
|
42
30
|
text_fields.first.set(number_of_codes.to_s)
|
43
31
|
click_next
|
32
|
+
|
33
|
+
# are there any errors ?
|
34
|
+
errors = []
|
35
|
+
begin
|
36
|
+
errors = wait_for_elements("div[id=LCPurpleSoftwarePageWrapperErrorMessage]")
|
37
|
+
rescue Exception
|
38
|
+
end
|
39
|
+
raise errors.first.text.red unless errors.count == 0
|
40
|
+
|
44
41
|
Helper.log.debug "Accepting the App Store Volume Custom Code Agreement"
|
45
42
|
wait_for_elements("input[type=checkbox]").first.click
|
46
43
|
click_next
|
@@ -48,23 +45,69 @@ module Codes
|
|
48
45
|
# the find(:xpath, "..") gets the parent element of the previous expression
|
49
46
|
download_url = wait_for_elements("div[class='large-blue-rect-button']").first.find(:xpath, '..')['href']
|
50
47
|
|
51
|
-
codes = download_codes
|
48
|
+
codes, request_date = download_codes(download_url)
|
52
49
|
|
53
|
-
|
54
|
-
|
55
|
-
code +" - "+ CODE_URL.gsub("[[code]]", code)
|
56
|
-
end
|
57
|
-
codes = codes.join("\n") + "\n"
|
58
|
-
end
|
50
|
+
format = args[:format]
|
51
|
+
codes = download_format(codes, format, request_date, app) if format
|
59
52
|
|
60
53
|
bytes_written = File.write(output_file_path.to_s, codes, mode: "a+")
|
61
54
|
Helper.log.warn "Could not write your codes to the codes.txt file, but you can still access them from iTunes Connect later" if bytes_written == 0
|
62
55
|
Helper.log.info "Added generated codes to '#{output_file_path.to_s}'".green unless bytes_written == 0
|
63
56
|
|
64
|
-
Helper.log.info "Your codes were successfully downloaded:".green
|
57
|
+
Helper.log.info "Your codes (requested #{request_date}) were successfully downloaded:".green
|
65
58
|
puts codes
|
66
59
|
end
|
67
60
|
|
61
|
+
def download_format(codes, format, request_date, app)
|
62
|
+
format = format.gsub(/%([a-z])/, "%{\\1}") # %c => %{c}
|
63
|
+
|
64
|
+
codes = codes.split("\n").map do |code|
|
65
|
+
format % {
|
66
|
+
c: code,
|
67
|
+
b: app['bundleId'],
|
68
|
+
d: request_date, # e.g. 20150520110716 / Cupertino timestamp...
|
69
|
+
i: app['trackId'],
|
70
|
+
n: "\'#{app['trackName']}\'",
|
71
|
+
p: app_platform(app),
|
72
|
+
u: CODE_URL.gsub("[[code]]", code)
|
73
|
+
}
|
74
|
+
end
|
75
|
+
codes = codes.join("\n") + "\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
def app_platform(app)
|
79
|
+
app['kind'] == "mac-software" ? "osx" : "ios"
|
80
|
+
end
|
81
|
+
|
82
|
+
def display(args)
|
83
|
+
Helper.log.info "Displaying remaining number of codes promo"
|
84
|
+
|
85
|
+
fetch_app_data args
|
86
|
+
|
87
|
+
# Use Pathname because it correctly handles the distinction between relative paths vs. absolute paths
|
88
|
+
output_file_path = Pathname.new(args[:output_file_path]) if args[:output_file_path]
|
89
|
+
output_file_path ||= Pathname.new(File.join(Dir.getwd, "#{@app_identifier || @app_id}_codes_info.txt"))
|
90
|
+
raise "Insufficient permissions to write to output file".red if File.exists?(output_file_path) and not File.writable?(output_file_path)
|
91
|
+
visit PROMO_URL.gsub("[[app_id]]", @app_id.to_s).gsub("[[platform]]", @platform)
|
92
|
+
|
93
|
+
begin
|
94
|
+
text_fields = wait_for_elements("input[type=text]")
|
95
|
+
rescue
|
96
|
+
raise "Could not open details page for app #{app_identifier}. Are you sure you are using the correct apple account and have access to this app?".red
|
97
|
+
end
|
98
|
+
raise "There should only be a single text input field to specify the number of codes".red unless text_fields.count == 1
|
99
|
+
|
100
|
+
remaining_divs = wait_for_elements("div#codes_0")
|
101
|
+
raise "There should only be a single text div containing the number of remaining codes".red unless remaining_divs.count == 1
|
102
|
+
remaining = remaining_divs.first.text.split(" ")[0]
|
103
|
+
|
104
|
+
bytes_written = File.write(output_file_path.to_s, remaining, mode: "a+")
|
105
|
+
Helper.log.warn "Could not write your codes to the codes_info.txt file, but you can still access them from iTunes Connect later" if bytes_written == 0
|
106
|
+
Helper.log.info "Added information of quantity of remaining codes to '#{output_file_path.to_s}'".green unless bytes_written == 0
|
107
|
+
|
108
|
+
puts remaining
|
109
|
+
end
|
110
|
+
|
68
111
|
def click_next
|
69
112
|
wait_for_elements("input.continueActionButton").first.click
|
70
113
|
end
|
@@ -79,8 +122,28 @@ module Codes
|
|
79
122
|
cookie_string << "#{cookie.name}=#{cookie.value};"
|
80
123
|
end
|
81
124
|
|
82
|
-
open(url, "Cookie" => cookie_string)
|
125
|
+
page = open(url, "Cookie" => cookie_string)
|
126
|
+
request_date = page.metas['content-disposition'][0].gsub(/.*filename=.*_(.*).txt/,"\\1")
|
127
|
+
codes = page.read
|
128
|
+
|
129
|
+
return codes, request_date
|
83
130
|
end
|
84
131
|
|
132
|
+
private
|
133
|
+
|
134
|
+
def fetch_app_data(args)
|
135
|
+
@country = args[:country]
|
136
|
+
@app_identifier = args[:app_identifier]
|
137
|
+
|
138
|
+
@app_id = args[:apple_id]
|
139
|
+
@app_id ||= (FastlaneCore::ItunesSearchApi.fetch_by_identifier(@app_identifier, @country)['trackId'] rescue nil)
|
140
|
+
|
141
|
+
if @app_id.to_i == 0 or @app_identifier.to_s.length == 0
|
142
|
+
raise "Could not find app using the following information: #{args}. Maybe the app is not in the store. Pass the Apple ID of the app as well!".red
|
143
|
+
end
|
144
|
+
|
145
|
+
app = FastlaneCore::ItunesSearchApi.fetch(@app_id, @country)
|
146
|
+
@platform = app_platform app
|
147
|
+
end
|
85
148
|
end
|
86
149
|
end
|
data/lib/codes/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: codes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Felix Krause
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-09-
|
11
|
+
date: 2015-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fastlane_core
|