codes 0.3.0 → 0.4.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.
- 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
|